主模式第四包:main_inI2_outR2

1. 序言

main_inI2_outR2()函数是ISAKMP协商过程中第四包的核心处理函数的入口,同时在此处理流程中已经获取到足够的隧道信息,可以生成需要的密钥信息。这里我们主要说明main_inI2_outR2的函数调用关系、处理流程以及对源码的注释分析,关于main_inI2_outR2的上下文环境暂不叙述,留给后面的文章进行更新。

ISAKMP协商报文的处理流程都比较复杂,此函数在协商的报文处理函数中比较复杂的,因此个人学习期间难免有遗漏和理解错误的地方,请大家多多批评指正。

对于源码的学习,我并没有把每一行进行备注,而是将自己认为的关键点做了注释或者标注。

2.函数调用关系

暂略。(此流程调用比较多,后面会在补充上)

3. 第四个报文流程图

第四个报文处理流程大致可以划分为四类功能:

  • 解析收到的第三个报文内容
  • 生成随机数(本段的KE和Nonce值)
  • 构造应答报文
  • 使用DH算法制作秘钥(三把钥匙)

整理的处理流程如下:

个人觉得这个流程做的还是很清晰和准确的(原谅我没有上色)

4. main_inI2_outR2源码分析

  1. stf_status
  2. main_inI2_outR2(struct msg_digest *md)
  3. {
  4. struct state *const st = md->st;
  5. pb_stream *keyex_pbs = &md->chain[ISAKMP_NEXT_KE]->pbs;
  6. /* if we are already processing a packet on this st, we will be unable
  7. * to start another crypto operation below */
  8. if (is_suspended(st)) {/*为了方式该流程处理时间过长导致对端超时重发*/
  9. openswan_log("%s: already processing a suspended cyrpto operation "
  10. "on this SA, duplicate will be dropped.", __func__);
  11. return STF_TOOMUCHCRYPTO;
  12. }
  13. /* KE in *//*从报文中获取KE载荷,并填充到st->st_gi上*/
  14. RETURN_STF_FAILURE(accept_KE(&st->st_gi, "Gi"
  15. , st->st_oakley.group, keyex_pbs));
  16. /* Ni in *//*从报文中获取Nonce载荷,并填充到st->st_ni上*/
  17. RETURN_STF_FAILURE(accept_v1_nonce(md, &st->st_ni, "Ni"));
  18. /* decode certificate requests *//*解析证书载荷,以链表的方式存储在st->st_connection->requested_ca*/
  19. ikev1_decode_cr(md, &st->st_connection->ikev1_requested_ca_names);
  20. if(st->st_connection->requested_ca != NULL)
  21. {
  22. st->hidden_variables.st_got_certrequest = TRUE;
  23. }
  24. #ifdef NAT_TRAVERSAL
  25. DBG(DBG_NATT
  26. , DBG_log("inI2: checking NAT-T: %d and %d"
  27. , nat_traversal_enabled
  28. , st->hidden_variables.st_nat_traversal));
  29. if (st->hidden_variables.st_nat_traversal & NAT_T_WITH_NATD) {
  30. DBG(DBG_NATT, DBG_log(" NAT_T_WITH_NATD detected"));
  31. nat_traversal_natd_lookup(md);/*根据哈希值确定是否经过NAT;状态上的NAT-T标志在此处做的修改*/
  32. }
  33. if (st->hidden_variables.st_nat_traversal) {/*打印NAT-T、端口浮动相关信息*/
  34. nat_traversal_show_result(st->hidden_variables.st_nat_traversal
  35. , md->sender_port);
  36. }
  37. if (st->hidden_variables.st_nat_traversal & NAT_T_WITH_KA) {
  38. DBG(DBG_NATT, DBG_log(" NAT_T_WITH_KA detected"));
  39. nat_traversal_new_ka_event();/*添加NAT-T的保活事件*/
  40. }
  41. #endif
  42. {
  43. struct ke_continuation *ke = alloc_thing(struct ke_continuation
  44. , "inI2_outR2 KE");
  45. ke->md = md;
  46. set_suspended(st, md);
  47. passert(st->st_sec_in_use == FALSE);
  48. pcrc_init(&ke->ke_pcrc);
  49. ke->ke_pcrc.pcrc_func = main_inI2_outR2_continue;
  50. return build_ke(&ke->ke_pcrc, st
  51. , st->st_oakley.group, st->st_import);
  52. }
  53. }

5. nat_traversal_natd_lookup源码分析

  1. /*检查是否需要经过NAT-T*/
  2. void nat_traversal_natd_lookup(struct msg_digest *md)
  3. {
  4. unsigned char hash_me[MAX_DIGEST_LEN];
  5. unsigned char hash_him[MAX_DIGEST_LEN];
  6. struct payload_digest *p;
  7. struct state *st = md->st;
  8. bool found_me = FALSE;
  9. bool found_him= FALSE;
  10. int i;
  11. passert(st);
  12. passert(md->iface);
  13. passert(st->st_oakley.prf_hasher);
  14. /** Count NAT-D **/
  15. for (p = md->chain[ISAKMP_NEXT_NATD_RFC], i=0;
  16. p != NULL;
  17. p = p->next, i++);/*统计NAT-D的数量*/
  18. /**
  19. * We need at least 2 NAT-D (1 for us, many for peer)
  20. */
  21. if (i < 2) {
  22. loglog(RC_LOG_SERIOUS,
  23. "NAT-Traversal: Only %d NAT-D - Aborting NAT-Traversal negotiation", i);
  24. st->hidden_variables.st_nat_traversal = 0;
  25. return;
  26. }
  27. /**
  28. * First one with my IP & port
  29. */
  30. _natd_hash(st->st_oakley.prf_hasher, hash_me
  31. , st->st_icookie, st->st_rcookie
  32. , &(md->iface->ip_addr)
  33. , ntohs(md->iface->port));
  34. /**
  35. * The others with sender IP & port
  36. */
  37. _natd_hash(st->st_oakley.prf_hasher, hash_him
  38. , st->st_icookie, st->st_rcookie
  39. , &(md->sender), ntohs(md->sender_port));
  40. for (p = md->chain[ISAKMP_NEXT_NATD_RFC], i=0;
  41. p != NULL && (!found_me || !found_him);
  42. p = p->next)
  43. {
  44. DBG(DBG_NATT,
  45. DBG_log("NAT_TRAVERSAL hash=%d (me:%d) (him:%d)"
  46. , i, found_me, found_him);
  47. DBG_dump("expected NAT-D(me):", hash_me,
  48. st->st_oakley.prf_hasher->hash_digest_len);
  49. DBG_dump("expected NAT-D(him):", hash_him,
  50. st->st_oakley.prf_hasher->hash_digest_len);
  51. DBG_dump("received NAT-D:", p->pbs.cur, pbs_left(&p->pbs));
  52. );
  53. if ( (pbs_left(&p->pbs) == st->st_oakley.prf_hasher->hash_digest_len)
  54. && (memcmp(p->pbs.cur, hash_me
  55. , st->st_oakley.prf_hasher->hash_digest_len)==0))
  56. {
  57. found_me = TRUE;/*本端未经过NAT*/
  58. }
  59. if ( (pbs_left(&p->pbs) == st->st_oakley.prf_hasher->hash_digest_len)
  60. && (memcmp(p->pbs.cur, hash_him
  61. , st->st_oakley.prf_hasher->hash_digest_len)==0))
  62. {
  63. found_him = TRUE;/*对端未经过NAT*/
  64. }
  65. i++;
  66. }
  67. DBG(DBG_NATT,
  68. DBG_log("NAT_TRAVERSAL hash=%d (me:%d) (him:%d)"
  69. , i, found_me, found_him));
  70. if(!found_me) {
  71. st->hidden_variables.st_nat_traversal |= LELEM(NAT_TRAVERSAL_NAT_BHND_ME);/*本端经过NAT*/
  72. st->hidden_variables.st_natd = md->sender;
  73. }
  74. memset(&st->hidden_variables.st_natd,0,sizeof(st->hidden_variables.st_natd));
  75. anyaddr(AF_INET, &st->hidden_variables.st_natd);
  76. if(!found_him) {
  77. st->hidden_variables.st_nat_traversal |= LELEM(NAT_TRAVERSAL_NAT_BHND_PEER);/*对端经过NAT*/
  78. st->hidden_variables.st_natd = md->sender;
  79. }
  80. if(st->st_connection->forceencaps) {/*如果需要强制使用UDP封装双方都需要NAT-D*/
  81. DBG(DBG_NATT,
  82. DBG_log("NAT_TRAVERSAL forceencaps enabled"));
  83. st->hidden_variables.st_nat_traversal |= LELEM(NAT_TRAVERSAL_NAT_BHND_PEER);
  84. st->hidden_variables.st_nat_traversal |= LELEM(NAT_TRAVERSAL_NAT_BHND_ME);
  85. st->hidden_variables.st_natd = md->sender;
  86. }
  87. }

6. build_ke源码分析

  1. stf_status build_ke(struct pluto_crypto_req_cont *cn
  2. , struct state *st
  3. , const struct oakley_group_desc *group
  4. , enum crypto_importance importance)
  5. {
  6. struct pluto_crypto_req rd;
  7. struct pluto_crypto_req *r = &rd;
  8. err_t e;
  9. bool toomuch = FALSE;
  10. /*初始化加密请求*/
  11. pcr_init(r, pcr_build_kenonce, importance);
  12. r->pcr_d.kn.oakley_group = group->group;
  13. cn->pcrc_serialno = st->st_serialno;
  14. /*申请制作加密材料请求*/
  15. e= send_crypto_helper_request(r, cn, &toomuch);
  16. if(e != NULL) {/*加密失败*/
  17. loglog(RC_LOG_SERIOUS, "can not start crypto helper: %s", e);
  18. if(toomuch) {
  19. return STF_TOOMUCHCRYPTO;
  20. } else {
  21. return STF_FAIL;
  22. }
  23. } else if(!toomuch) {/*加密任务繁忙,先挂起等待再次调度*/
  24. st->st_calculating = TRUE;
  25. delete_event(st);
  26. event_schedule(EVENT_CRYPTO_FAILED, EVENT_CRYPTO_FAILED_DELAY, st);
  27. return STF_SUSPEND;
  28. } else {
  29. /* we must have run the continuation directly, so
  30. * complete_v1_state_transition already got called.
  31. * 由于我们已经手动执行了main_inR1_outI2_continue(),该函数最终会调用到complete_v1_state_transition
  32. * 因此在(process_v1_state_xxx流程中不必再此执行状态转换函数。因此返回STF_INLINE,当再次到complete_v1_state_transition
  33. *判断返回值为它,则不再执行此函数。)
  34. */
  35. return STF_INLINE;
  36. }
  37. }

7. main_inI2_outR2_continue源码分析

  1. static void
  2. main_inI2_outR2_continue(struct pluto_crypto_req_cont *pcrc
  3. , struct pluto_crypto_req *r
  4. , err_t ugh)
  5. {
  6. struct ke_continuation *ke = (struct ke_continuation *)pcrc;
  7. struct msg_digest *md = ke->md;
  8. struct state *const st = md->st;
  9. stf_status e;
  10. ... ...
  11. set_suspended(st, NULL); /* no longer connected or suspended */
  12. set_cur_state(st);
  13. st->st_calculating = FALSE;
  14. e = main_inI2_outR2_tail(pcrc, r);/*构造应答报文*/
  15. if(ke->md != NULL) {
  16. complete_v1_state_transition(&ke->md, e);/*发送报文并完后后续处理工作*/
  17. if(ke->md) release_md(ke->md);
  18. }
  19. reset_cur_state();
  20. }

8. main_inI2_outR2_tail源码分析

  1. /*
  2. * this routine gets called after any DH exponentiation that needs to be done
  3. * has been done, and we are ready to send our g^y.
  4. */
  5. stf_status
  6. main_inI2_outR2_tail(struct pluto_crypto_req_cont *pcrc
  7. , struct pluto_crypto_req *r)
  8. {
  9. struct ke_continuation *ke = (struct ke_continuation *)pcrc;
  10. struct msg_digest *md = ke->md;
  11. struct state *st = md->st;
  12. /* send CR if auth is RSA and no preloaded RSA public key exists*/
  13. bool send_cr = FALSE;
  14. /**************** build output packet HDR;KE;Nr ****************/
  15. /*如果以下四个条件同时满足,则通过需要发送证书。。*/
  16. send_cr = !no_cr_send /*配置中允许发送证书*/
  17. && (st->st_oakley.auth == OAKLEY_RSA_SIG) /*使用RSA签名*/
  18. && !has_preloaded_public_key(st) /*未加载未共享秘钥*/
  19. && st->st_connection->spd.that.ca.ptr != NULL; /*对端证书非空*/
  20. /* HDR out */
  21. echo_hdr(md, FALSE, ISAKMP_NEXT_KE);
  22. /* KE out *//*添加KE载荷,并将其存储在st->st_gr*/
  23. if (!ship_KE(st, r, &st->st_gr
  24. , &md->rbody, ISAKMP_NEXT_NONCE))
  25. {
  26. osw_abort();
  27. return STF_INTERNAL_ERROR;
  28. }
  29. #ifdef DEBUG
  30. {
  31. /* Nr out */
  32. int next_payload;
  33. next_payload = ISAKMP_NEXT_NONE;
  34. if(cur_debugging & IMPAIR_BUST_MR2)
  35. {
  36. next_payload = ISAKMP_NEXT_VID;
  37. }
  38. if(send_cr)
  39. {
  40. next_payload = ISAKMP_NEXT_CR;
  41. }
  42. if (!ship_nonce(&st->st_nr, r
  43. , &md->rbody
  44. , next_payload
  45. , "Nr"))
  46. return STF_INTERNAL_ERROR;
  47. if (cur_debugging & IMPAIR_BUST_MR2)
  48. {
  49. /* generate a pointless large VID payload to push message over MTU */
  50. pb_stream vid_pbs;
  51. if (!out_generic((send_cr)? ISAKMP_NEXT_CR : ISAKMP_NEXT_NONE,
  52. &isakmp_vendor_id_desc, &md->rbody, &vid_pbs))
  53. return STF_INTERNAL_ERROR;
  54. if (!out_zero(1500 /*MTU?*/, &vid_pbs, "Filler VID"))
  55. return STF_INTERNAL_ERROR;
  56. close_output_pbs(&vid_pbs);
  57. }
  58. }
  59. #else
  60. /* Nr out *//*添加NONCE载荷,并将其存储在st->st_nr*/
  61. if (!ship_nonce(&st->st_nr, r
  62. , &md->rbody
  63. , (send_cr)? ISAKMP_NEXT_CR : ISAKMP_NEXT_NONE
  64. , "Nr"))
  65. return STF_INTERNAL_ERROR;
  66. #endif
  67. /* CR out *//*如果需要发送证书*/
  68. if (send_cr)
  69. {
  70. if (st->st_connection->kind == CK_PERMANENT)/*双方连接固定,即两端的IP的确定的*/
  71. {
  72. if (!build_and_ship_CR(CERT_X509_SIGNATURE /*添加对端证书载荷*/
  73. , st->st_connection->spd.that.ca
  74. , &md->rbody, ISAKMP_NEXT_NONE))
  75. return STF_INTERNAL_ERROR;
  76. }
  77. else
  78. {
  79. generalName_t *ca = NULL;
  80. /*查询可用的证书 ???*/
  81. if (collect_rw_ca_candidates(md, &ca))/*收集所有可用证书,并全部加载到ca链表上*/
  82. {
  83. generalName_t *gn;
  84. for (gn = ca; gn != NULL; gn = gn->next)
  85. {
  86. if (!build_and_ship_CR(CERT_X509_SIGNATURE, gn->name/*将所有的可用证书加载到链表上*/
  87. , &md->rbody
  88. , gn->next == NULL ? ISAKMP_NEXT_NONE : ISAKMP_NEXT_CR))
  89. return STF_INTERNAL_ERROR;
  90. }
  91. free_generalNames(ca, FALSE);/*释放可用证书链表*/
  92. }
  93. else
  94. {/*确实没有找到可用的证书,则填充一个空的证书载荷*/
  95. if (!build_and_ship_CR(CERT_X509_SIGNATURE, empty_chunk
  96. , &md->rbody, ISAKMP_NEXT_NONE))
  97. return STF_INTERNAL_ERROR;
  98. }
  99. }
  100. }
  101. #ifdef NAT_TRAVERSAL
  102. if (st->hidden_variables.st_nat_traversal & NAT_T_WITH_NATD) {
  103. if (!nat_traversal_add_natd(ISAKMP_NEXT_NONE, &md->rbody, md))/*添加NAT-D载荷*/
  104. return STF_INTERNAL_ERROR;
  105. }
  106. #endif
  107. /* finish message */
  108. close_message(&md->rbody);
  109. /*********************************************
  110. 使 用 DH 算 法 开 始 制 作 密 钥
  111. ***********************************************/
  112. /*
  113. * next message will be encrypted, so, we need to have
  114. * the DH value calculated. We can do this in the background,
  115. * sending the reply right away. We have to be careful on the next
  116. * state, since the other end may reply faster than we can calculate
  117. * things. If it is the case, then the packet is placed in the
  118. * continuation, and we let the continuation process it. If there
  119. * is a retransmit, we keep only the last packet.
  120. *
  121. * Also, note that this is not a suspended state, since we are
  122. * actually just doing work in the background.
  123. *
  124. */
  125. {
  126. /* Looks like we missed perform_dh() declared at
  127. * programs/pluto/pluto_crypt.h as external and implemented nowhere.
  128. * Following code regarding dh_continuation allocation seems useless
  129. * as it's never used. At least, we should free it.
  130. */
  131. struct dh_continuation *dh = alloc_thing(struct dh_continuation
  132. , "main_inI2_outR2_tail");
  133. stf_status e;
  134. dh->md = NULL;
  135. dh->serialno = st->st_serialno;
  136. pcrc_init(&dh->dh_pcrc);
  137. dh->dh_pcrc.pcrc_func = main_inI2_outR2_calcdone;
  138. passert(st->st_suspended_md == NULL);
  139. DBG(DBG_CONTROLMORE
  140. , DBG_log("main inI2_outR2: starting async DH calculation (group=%d)", st->st_oakley.group->group));
  141. e = start_dh_secretiv(&dh->dh_pcrc, st
  142. , st->st_import
  143. , RESPONDER
  144. , st->st_oakley.group->group);
  145. DBG(DBG_CONTROLMORE,
  146. DBG_log("started dh_secretiv, returned: stf=%s\n"
  147. , stf_status_name(e)));
  148. if(e == STF_FAIL) {
  149. loglog(RC_LOG_SERIOUS, "failed to start async DH calculation, stf=%s\n"
  150. , stf_status_name(e));
  151. return e;
  152. }
  153. /* we are calculating in the background, so it doesn't count */
  154. if(e == STF_SUSPEND) {
  155. st->st_calculating = FALSE;
  156. }
  157. }
  158. return STF_OK;
  159. }

9. main_inI2_outR2_calcdone源码分析

  1. static void
  2. main_inI2_outR2_calcdone(struct pluto_crypto_req_cont *pcrc
  3. , struct pluto_crypto_req *r
  4. , err_t ugh)
  5. {
  6. struct dh_continuation *dh = (struct dh_continuation *)pcrc;
  7. struct state *st;
  8. DBG(DBG_CONTROLMORE
  9. , DBG_log("main inI2_outR2: calculated DH finished"));
  10. st = state_with_serialno(dh->serialno);
  11. if(st == NULL) {
  12. openswan_log("state %ld disappeared during crypto\n", dh->serialno);
  13. return;
  14. }
  15. set_cur_state(st);
  16. if(ugh) {
  17. loglog(RC_LOG_SERIOUS, "DH crypto failed: %s\n", ugh);
  18. return;
  19. }
  20. /*将生成的三把秘钥、DH-IV等信息存储在状态上*/
  21. finish_dh_secretiv(st, r);
  22. if(!r->pcr_success) {
  23. loglog(RC_LOG_SERIOUS, "DH crypto failed, invalid keys");
  24. return;
  25. }
  26. ikev2_validate_key_lengths(st);
  27. st->hidden_variables.st_skeyid_calculated = TRUE;
  28. update_iv(st);/*更新IV值*/
  29. /* XXX: Do we need to free dh here? If so, how about the other exits?
  30. * pfree(dh); dh = NULL;
  31. */
  32. /*
  33. * if there was a packet received while we were calculating, then
  34. * process it now.
  35. */
  36. /*如果在计算秘钥的过程中收到新的报文则现在再处理该报文*/
  37. if(st->st_suspended_md != NULL) {
  38. struct msg_digest *md = st->st_suspended_md;
  39. set_suspended(st, NULL);
  40. process_packet_tail(&md);
  41. if(md != NULL) {
  42. release_md(md);
  43. }
  44. }
  45. reset_cur_state();
  46. return;
  47. }

openswan协商流程之(四):main_inI2_outR2()的更多相关文章

  1. openswan协商流程之(七):main_inR3

    主模式第六包(收包):main_inR3 1. 序言 main_inR3()函数是ISAKMP协商过程中第一阶段的最后一个报文的接收处理函数,它的作用同main_inI3_outR3()部分功能相同: ...

  2. openswan协商流程之(一):main_outI1()

    主模式第一包:main_outI1() 1. 序言 main_outI1()作为主模式主动发起连接请求的核心处理函数,我们可以通过学习该函数的处理流程来探究openswan中报文封装的基本思想.如果之 ...

  3. openswan协商流程之(五):main_inR2_outI3()

    主模式第五包:main_inR2_outI3 文章目录 主模式第五包:main_inR2_outI3 1. 序言 2.函数调用关系 3. 第五个报文流程图 4. main_inR2_outI3()源码 ...

  4. openswan协商流程之(三):main_inR1_outI2

    主模式第三包:main_inR1_outI2 1. 序言 main_inR1_outI2()函数是ISAKMP协商过程中第三包的核心处理函数的入口.这里我们主要说明main_inR1_outI2的函数 ...

  5. openswan协商流程之(六):main_inI3_outR3()

    主模式第六包:main_inI3_outR3 1. 序言 main_inI3_outR3()函数是ISAKMP协商过程中第六包的核心处理函数的入口,第五六包主要用来验证对方的身份信息,同时此报文也是加 ...

  6. openswan协商流程之(二):main_inI1_outR1()

    主模式第二包:main_inI1_outR1() 文章目录 主模式第二包:main_inI1_outR1() 1. 序言 2. `main_inI1_outR1()`处理流程图 3. `main_in ...

  7. IKEv2协议协商流程: (IKE-SA-INIT 交换)第二包

    IKEv2协议协商流程: (IKE-SA-INIT 交换)第二包 文章目录 IKEv2协议协商流程: (IKE-SA-INIT 交换)第二包 1. IKEv2 协商总体框架 2. 第二包流程图 3. ...

  8. 一个简单的Eclipse调试Debug流程(四)

    本文链接:https://blog.csdn.net/u011781521/article/details/55000066    http://blog.csdn.net/u010075335/ar ...

  9. 以太网驱动的流程浅析(四)-以太网驱动probe流程【原创】

    以太网驱动的流程浅析(四)-以太网驱动probe流程 Author:张昺华 Email:920052390@qq.com Time:2019年3月23日星期六 此文也在我的个人公众号以及<Lin ...

随机推荐

  1. linux对拍

    转自 hzoi-fengwu :https://www.cnblogs.com/hzoi-fengwu/p/14872962.html 1 #include<bits/stdc++.h> ...

  2. JAVA虚拟机的组成>从零开始学java系列

    目录 JAVA虚拟机的组成 什么是虚拟机? JAVA虚拟机的组成部分 堆区(堆内存) 方法区 虚拟机栈 本地方法栈 程序计数器 字符串常量池 JAVA虚拟机的组成 什么是虚拟机? 虚拟机是运行在隔离环 ...

  3. C++ //继承同名成员处理方式

    1 #include <iostream> 2 #include <string> 3 using namespace std; 4 5 class Base 6 { 7 pu ...

  4. Lock(锁)

    Lock(锁) 从JDK 5.0开始,Java提供了更加强大的线程同步机制----通过显示定义同步锁对象来实现同步.同步锁使用Lock对象充当. java.util.concurrent.locks. ...

  5. 采用Jpcap+redis+线程 设备网络流量监控 应用实战实例

    .personSunflowerP { background: rgba(51, 153, 0, 0.66); border-bottom: 1px solid rgba(0, 102, 0, 1); ...

  6. Python语言系列-03-文件操作和函数

    ## 深浅拷贝 #!/usr/bin/env python3 # author:Alnk(李成果) # 赋值运算 # 可变的数据类型:由于数据类型可变,修改数据会在原来的数据的基础上进行修改, # 可 ...

  7. 关于解决numpy使用sklearn时的警告问题

    关于解决numpy使用sklearn时的警告问题 在使用的时候,出现提示 :219: RuntimeWarning: numpy.ufunc size changed, may indicate bi ...

  8. 深度学习框架如何自动选择最快的算法?Fast Run 让你收获最好的性能!

    作者:王博文 | 旷视 MegEngine 架构师 一.背景 对于深度学习框架来说,网络的训练/推理时间是用户非常看中的.在实际生产条件下,用户设计的 NN 网络是千差万别,即使是同一类数学计算,参数 ...

  9. Java实现WebSocket服务

    一.使用Tomcat提供的WebSocket库  Java可以使用Tomcat提供的WebSocket库接口实现WebSocket服务,代码编写也非常的简单.现在的H5联网游戏基本上都是使用WebSo ...

  10. kubebuilder实战之二:初次体验kubebuilder

    欢迎访问我的GitHub https://github.com/zq2599/blog_demos 内容:所有原创文章分类汇总及配套源码,涉及Java.Docker.Kubernetes.DevOPS ...