handshake_manager_hybrid.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455
  1. package nebula
  2. import (
  3. "context"
  4. "errors"
  5. "log/slog"
  6. "net/netip"
  7. "sync"
  8. "time"
  9. "github.com/slackhq/nebula/cert"
  10. "github.com/slackhq/nebula/handshake"
  11. "github.com/slackhq/nebula/handshake/fragment"
  12. "github.com/slackhq/nebula/header"
  13. )
  14. // Hybrid handshakes (ix_hybrid_mlkem1024) take three messages, so unlike IX the
  15. // responder has to wait for one more message and the initiator finishes by
  16. // sending rather than receiving:
  17. //
  18. // - The responder answers msg1 with msg2 and keeps the handshake pending under
  19. // its local index until msg3 arrives. Data for that index is dropped quietly
  20. // meanwhile, it must not draw a RecvError. Once msg3 verifies the tunnel
  21. // goes live and an empty TestReply tells the initiator so.
  22. // - The initiator has the keys as soon as it writes msg3, so the tunnel goes
  23. // live then, readable only by the real responder. msg3 is resent with fresh
  24. // symbols until something from the responder decrypts, and if nothing does
  25. // within the handshake timeout the tunnel is closed.
  26. const (
  27. // hybridMsg3MaxLen bounds the msg3 a pending responder reassembles. msg3 is
  28. // a ciphertext and two tags, about 1.6KB.
  29. hybridMsg3MaxLen = 4096
  30. )
  31. // hybridResponder is the responder half of a pending hybrid handshake, waiting
  32. // for msg3.
  33. type hybridResponder struct {
  34. msg3 *fragment.Assembly
  35. expires time.Time
  36. }
  37. // hybridConfirm is an initiator's completed hybrid handshake that has not yet
  38. // heard from the responder.
  39. type hybridConfirm struct {
  40. hostinfo *HostInfo
  41. subtype header.MessageSubType
  42. sender *fragment.Sender
  43. send func([]byte) error
  44. handshakeLen uint64 // messages the handshake took, which the replay window was seeded with
  45. deadline time.Time
  46. next time.Time
  47. attempts int
  48. }
  49. // hybridState holds the hybrid handshakes waiting on their last step.
  50. type hybridState struct {
  51. // responders maps local indexes to pending responders. Guarded by the
  52. // HandshakeManager lock; the entries are also in its indexes.
  53. responders map[uint32]*HandshakeHostInfo
  54. confirmsMu sync.Mutex
  55. confirms map[*HostInfo]*hybridConfirm
  56. // answered holds when this responder answered each peer's latest new
  57. // hybrid msg1, for the crossing window.
  58. answeredMu sync.Mutex
  59. answered map[netip.Addr]time.Time
  60. }
  61. func newHybridState() hybridState {
  62. return hybridState{
  63. responders: map[uint32]*HandshakeHostInfo{},
  64. confirms: map[*HostInfo]*hybridConfirm{},
  65. answered: map[netip.Addr]time.Time{},
  66. }
  67. }
  68. // A peer that sent a hybrid msg1 alternates, and its first classical attempt
  69. // follows anywhere from right away to three retry intervals later: the retry
  70. // timer is set one interval out, lands two slots ahead on the timer wheel, and
  71. // the wheel only moves on whole ticks of the manager's clock. Answering that
  72. // classical msg1 races the hybrid handshake and leaves a classical tunnel behind
  73. // on the responder whichever wins. So for four retry intervals after answering a
  74. // peer's hybrid msg1, its classical msg1s go unanswered; the crossing one arrives
  75. // within that wherever the round trip, since both travel the same path. The
  76. // window is short on purpose: an initiator whose hybrid attempt fails on its own
  77. // side, say one that does not trust our v3 CA yet, still gets a classical answer
  78. // to a later attempt. Blocking for as long as any hybrid state remains would
  79. // refuse it until its handshake timed out, and its next handshake would start
  80. // with a hybrid msg1 again.
  81. // hybridWindow is how long classical msg1s go unanswered after a hybrid msg1.
  82. func (hm *HandshakeManager) hybridWindow() time.Duration {
  83. return 4 * hm.config.tryInterval
  84. }
  85. // noteHybridMsg1 opens the crossing window for a peer whose new hybrid msg1 this
  86. // responder answered.
  87. func (hm *HandshakeManager) noteHybridMsg1(vpnAddrs []netip.Addr, now time.Time) {
  88. hm.hybrid.answeredMu.Lock()
  89. defer hm.hybrid.answeredMu.Unlock()
  90. for _, a := range vpnAddrs {
  91. hm.hybrid.answered[a] = now
  92. }
  93. }
  94. // crossesHybrid reports whether a classical msg1 from a peer arrived within the
  95. // crossing window of its hybrid msg1.
  96. func (hm *HandshakeManager) crossesHybrid(vpnAddrs []netip.Addr, now time.Time) bool {
  97. hm.hybrid.answeredMu.Lock()
  98. defer hm.hybrid.answeredMu.Unlock()
  99. for _, a := range vpnAddrs {
  100. if at, ok := hm.hybrid.answered[a]; ok && now.Sub(at) < hm.hybridWindow() {
  101. return true
  102. }
  103. }
  104. return false
  105. }
  106. // awaitMsg3 keeps a responder's hybrid handshake pending after msg1, and sends
  107. // msg2. hh is already in the indexes under the local index the Machine
  108. // allocated.
  109. func (hm *HandshakeManager) awaitMsg3(via ViaSender, hh *HandshakeHostInfo, machine *handshake.Machine, packet, response []byte, received int) {
  110. f := hm.f
  111. hostinfo := hh.hostinfo
  112. remoteCert := machine.PeerCert()
  113. if remoteCert == nil {
  114. f.l.Error("Handshake did not produce a peer certificate", "from", via)
  115. hm.DeleteHostInfo(hostinfo)
  116. return
  117. }
  118. vpnAddrs, anyVpnAddrsInCommon, ok := hm.validatePeerCert(via, remoteCert)
  119. if !ok {
  120. hm.DeleteHostInfo(hostinfo)
  121. return
  122. }
  123. var rh header.H
  124. if err := rh.Parse(response); err != nil {
  125. hm.DeleteHostInfo(hostinfo)
  126. return
  127. }
  128. now := time.Now()
  129. hostinfo.vpnAddrs = vpnAddrs
  130. hostinfo.remoteIndexId = rh.RemoteIndex
  131. // A hybrid msg1 is always fragmented, so packet is the one the fragment
  132. // table reassembled, not the listener's buffer, and nothing else holds it.
  133. hostinfo.HandshakePacket[handshakePacketStage0] = packet[header.Len:]
  134. hostinfo.HandshakePacket[handshakePacketStage2] = response
  135. hostinfo.remotes = f.lightHouse.QueryCache(vpnAddrs)
  136. if !via.IsRelayed {
  137. hostinfo.SetRemote(via.UdpAddr)
  138. }
  139. hh.machine = machine
  140. hh.responder.expires = now.Add(hsTimeout(hm.config.retries, hm.config.tryInterval))
  141. hm.Lock()
  142. hm.hybrid.responders[hostinfo.localIndexId] = hh
  143. hm.Unlock()
  144. msg := "Handshake message received"
  145. if !anyVpnAddrsInCommon {
  146. msg = "Handshake message received, but no vpnNetworks in common."
  147. }
  148. f.l.Info(msg,
  149. "vpnAddrs", vpnAddrs,
  150. "from", via,
  151. "certName", remoteCert.Certificate.Name(),
  152. "certVersion", remoteCert.Certificate.Version(),
  153. "fingerprint", remoteCert.Fingerprint,
  154. "issuer", remoteCert.Certificate.Issuer(),
  155. "initiatorIndex", hostinfo.remoteIndexId,
  156. "responderIndex", hostinfo.localIndexId,
  157. "handshake", m{"stage": uint64(machine.MessageIndex()), "style": header.SubTypeName(header.Handshake, machine.Subtype())},
  158. )
  159. hm.sendHandshakeResponse(via, response, hostinfo, false, received)
  160. hm.pqAnswered.add(packet[header.Len:], hostinfo, now)
  161. hm.noteHybridMsg1(vpnAddrs, now)
  162. }
  163. // continueHybridResponder collects msg3 fragments for a pending responder and
  164. // completes the tunnel once they decode and verify.
  165. func (hm *HandshakeManager) continueHybridResponder(via ViaSender, h *header.H, fh fragment.Header, sym []byte) {
  166. hh := hm.queryIndex(h.RemoteIndex)
  167. if hh == nil || hh.responder == nil {
  168. hm.reconfirmHybrid(h.RemoteIndex)
  169. return
  170. }
  171. if fh.MsgLen > hybridMsg3MaxLen {
  172. return
  173. }
  174. hh.Lock()
  175. defer hh.Unlock()
  176. if !hm.stillPending(hh, h.RemoteIndex) || fh.FragID != hh.hostinfo.remoteIndexId {
  177. return
  178. }
  179. r := hh.responder
  180. if r.msg3 == nil {
  181. a, err := fragment.NewAssembly(fh)
  182. if err != nil {
  183. return
  184. }
  185. r.msg3 = a
  186. }
  187. ready, err := r.msg3.Add(fh, sym)
  188. if errors.Is(err, fragment.ErrMismatch) {
  189. r.msg3 = nil
  190. return
  191. }
  192. if err != nil || !ready {
  193. return
  194. }
  195. pkt, err := r.msg3.Decode(make([]byte, header.Len))
  196. r.msg3 = nil
  197. if err != nil {
  198. hm.l.Debug("failed to decode handshake fragments", "from", via, "error", err)
  199. return
  200. }
  201. _, result, err := hh.machine.ProcessPacket(nil, reassembled(h, pkt))
  202. if err != nil {
  203. if hh.machine.Failed() {
  204. hm.l.Warn("Failed to process handshake packet, abandoning",
  205. "vpnAddrs", hh.hostinfo.vpnAddrs, "from", via, "error", err)
  206. hm.forgetHybridResponder(hh)
  207. } else {
  208. hm.l.Debug("Failed to process handshake packet",
  209. "vpnAddrs", hh.hostinfo.vpnAddrs, "from", via, "error", err)
  210. }
  211. return
  212. }
  213. if result == nil {
  214. return
  215. }
  216. hm.completeHybridResponder(via, hh, result)
  217. }
  218. // completeHybridResponder brings up the tunnel of a responder whose msg3 verified.
  219. // The caller must hold hh.Lock.
  220. func (hm *HandshakeManager) completeHybridResponder(via ViaSender, hh *HandshakeHostInfo, result *handshake.Result) {
  221. f := hm.f
  222. hostinfo := hh.hostinfo
  223. machine := hh.machine
  224. defer hm.forgetHybridResponder(hh)
  225. cs, err := newConnectionStateFromResult(result)
  226. if err != nil {
  227. f.l.Error("Discarding handshake with an invalid message index", "error", err, "vpnAddrs", hostinfo.vpnAddrs)
  228. return
  229. }
  230. hostinfo.ConnectionState = cs
  231. hostinfo.remoteIndexId = result.RemoteIndex
  232. hostinfo.lastHandshakeTime = result.HandshakeTime
  233. // The initiator sends msg3 by the path its msg2 got through, so that path
  234. // works both ways: send by it. A relayed msg3 also means the direct address
  235. // learned from msg1 may only work toward us, so forget it and let traffic,
  236. // the confirmation first, go by the relay.
  237. if !via.IsRelayed {
  238. hostinfo.SetRemote(via.UdpAddr)
  239. } else {
  240. hostinfo.remote.Store(nil)
  241. hostinfo.relayState.InsertRelayTo(via.relayHI.vpnAddrs[0])
  242. }
  243. remoteCert := result.RemoteCert
  244. hostinfo.buildNetworks(f.myVpnNetworksTable, remoteCert.Certificate)
  245. // The initiator only sends msg3 once it settled on this handshake, so this
  246. // tunnel wins even over a newer one: a classical attempt that crossed it, say,
  247. // which the initiator has already given up on.
  248. existing, err := hm.checkAndComplete(hostinfo, handshakePacketStage0, f, true)
  249. if err != nil {
  250. hm.handleCheckAndCompleteError(err, existing, hostinfo, via, machine.Subtype(), 0)
  251. return
  252. }
  253. f.l.Info("Handshake completed",
  254. "vpnAddrs", hostinfo.vpnAddrs,
  255. "from", via,
  256. "certName", remoteCert.Certificate.Name(),
  257. "certVersion", remoteCert.Certificate.Version(),
  258. "fingerprint", remoteCert.Fingerprint,
  259. "issuer", remoteCert.Certificate.Issuer(),
  260. "initiatorIndex", result.RemoteIndex,
  261. "responderIndex", result.LocalIndex,
  262. "handshake", m{"stage": uint64(machine.MessageIndex()), "style": header.SubTypeName(header.Handshake, machine.Subtype())},
  263. "durationNs", time.Since(hh.startTime).Nanoseconds(),
  264. )
  265. // Let the initiator know, even if there is no traffic to do it.
  266. hostinfo.handshakeResendAt.Store(time.Now().UnixNano())
  267. hm.sendHybridConfirmation(hostinfo)
  268. hm.notePQPeer(machine.Subtype(), hostinfo.vpnAddrs)
  269. hostinfo.remotes.RefreshFromHandshake(hostinfo.vpnAddrs)
  270. if f.lightHouse.IsAnyLighthouseAddr(hostinfo.vpnAddrs) {
  271. f.lightHouse.TriggerUpdate()
  272. }
  273. }
  274. func (hm *HandshakeManager) sendHybridConfirmation(hostinfo *HostInfo) {
  275. // The reply is empty, so even relayed it is just two headers and two tags.
  276. cs := hostinfo.ConnectionState
  277. out := make([]byte, 2*(header.Len+cs.eKey.Overhead()))
  278. hm.f.send(header.Test, header.TestReply, cs, hostinfo, []byte{}, make([]byte, 12, 12), out)
  279. }
  280. // reconfirmHybrid answers a msg3 for a hybrid tunnel this responder already
  281. // completed: the initiator is still resending it, so the confirmation was lost.
  282. // Replies are rate limited to one per retry interval and go to the tunnel's
  283. // remote, never to wherever the fragment claims to come from.
  284. func (hm *HandshakeManager) reconfirmHybrid(index uint32) {
  285. hostinfo := hm.mainHostMap.QueryIndex(index)
  286. if hostinfo == nil || hostinfo.ConnectionState == nil || hostinfo.ConnectionState.initiator {
  287. return
  288. }
  289. if pc := hostinfo.ConnectionState.peerCert; pc == nil || pc.Certificate.Version() != cert.Version3 {
  290. return
  291. }
  292. now := time.Now().UnixNano()
  293. last := hostinfo.handshakeResendAt.Load()
  294. if now-last < int64(hm.config.tryInterval) || !hostinfo.handshakeResendAt.CompareAndSwap(last, now) {
  295. return
  296. }
  297. hm.sendHybridConfirmation(hostinfo)
  298. }
  299. // forgetHybridResponder releases a pending responder's index.
  300. func (hm *HandshakeManager) forgetHybridResponder(hh *HandshakeHostInfo) {
  301. hm.Lock()
  302. defer hm.Unlock()
  303. idx := hh.hostinfo.localIndexId
  304. if hm.indexes[idx] == hh {
  305. delete(hm.indexes, idx)
  306. }
  307. if hm.hybrid.responders[idx] == hh {
  308. delete(hm.hybrid.responders, idx)
  309. }
  310. }
  311. // isHybridResponder reports whether index belongs to a responder still waiting
  312. // for msg3, whose initiator may already be sending data.
  313. func (hm *HandshakeManager) isHybridResponder(index uint32) bool {
  314. hm.RLock()
  315. defer hm.RUnlock()
  316. _, ok := hm.hybrid.responders[index]
  317. return ok
  318. }
  319. // expireHybridResponders drops responders that never got their msg3.
  320. func (hm *HandshakeManager) expireHybridResponders(now time.Time) {
  321. hm.hybrid.answeredMu.Lock()
  322. for a, at := range hm.hybrid.answered {
  323. if now.Sub(at) >= hm.hybridWindow() {
  324. delete(hm.hybrid.answered, a)
  325. }
  326. }
  327. hm.hybrid.answeredMu.Unlock()
  328. hm.Lock()
  329. defer hm.Unlock()
  330. for idx, hh := range hm.hybrid.responders {
  331. if now.After(hh.responder.expires) {
  332. if hm.indexes[idx] == hh {
  333. delete(hm.indexes, idx)
  334. }
  335. delete(hm.hybrid.responders, idx)
  336. }
  337. }
  338. }
  339. // startHybridConfirm sends an initiator's msg3 and keeps resending it until the
  340. // responder is heard from on the new tunnel.
  341. func (hm *HandshakeManager) startHybridConfirm(via ViaSender, hostinfo *HostInfo, msg3 []byte, handshakeLen uint64) {
  342. var h header.H
  343. if err := h.Parse(msg3); err != nil {
  344. return
  345. }
  346. s, err := fragment.NewSender(msg3[header.Len:], h.Subtype, h.RemoteIndex, h.MessageCounter, hostinfo.localIndexId)
  347. if err != nil {
  348. hostinfo.logger(hm.l).Error("Failed to fragment handshake", "error", err)
  349. return
  350. }
  351. now := time.Now()
  352. c := &hybridConfirm{
  353. hostinfo: hostinfo,
  354. subtype: h.Subtype,
  355. sender: s,
  356. send: hm.sendTo(via),
  357. handshakeLen: handshakeLen,
  358. deadline: now.Add(hsTimeout(hm.config.retries, hm.config.tryInterval)),
  359. next: now.Add(hm.config.tryInterval),
  360. attempts: 1,
  361. }
  362. hm.hybrid.confirmsMu.Lock()
  363. hm.hybrid.confirms[hostinfo] = c
  364. hm.hybrid.confirmsMu.Unlock()
  365. hm.sendHybridMsg3(c)
  366. }
  367. func (hm *HandshakeManager) sendHybridMsg3(c *hybridConfirm) {
  368. pkts, later, err := c.sender.Attempt(fragment.DefaultRepair(c.sender.Layout().K))
  369. if err != nil {
  370. return
  371. }
  372. hm.messageMetrics.Tx(header.Handshake, c.subtype, int64(len(pkts)+len(later)))
  373. // No repair delay: the packets cached during the handshake go out right
  374. // behind msg3, and the responder drops whatever arrives before msg3
  375. // completes. With the repair trailing, losing either data fragment would
  376. // cost them. msg3 is four packets, too short for the tail drop the delay
  377. // guards against.
  378. if err := hm.sendFragmentedAfter(pkts, later, c.send, 0); err != nil {
  379. c.hostinfo.logger(hm.l).Debug("Failed to send handshake message", "error", err)
  380. }
  381. }
  382. // tickHybridConfirms resends msg3 to responders not heard from yet, and closes
  383. // tunnels whose responder never was.
  384. func (hm *HandshakeManager) tickHybridConfirms(now time.Time) {
  385. var resend, closing []*hybridConfirm
  386. hm.hybrid.confirmsMu.Lock()
  387. for hostinfo, c := range hm.hybrid.confirms {
  388. switch {
  389. case hm.mainHostMap.QueryIndex(hostinfo.localIndexId) != hostinfo:
  390. delete(hm.hybrid.confirms, hostinfo)
  391. case hostinfo.ConnectionState.heardFromPeer(c.handshakeLen):
  392. delete(hm.hybrid.confirms, hostinfo)
  393. case now.After(c.deadline):
  394. delete(hm.hybrid.confirms, hostinfo)
  395. closing = append(closing, c)
  396. case !now.Before(c.next):
  397. c.attempts++
  398. c.next = now.Add(hm.config.tryInterval * time.Duration(c.attempts))
  399. resend = append(resend, c)
  400. }
  401. }
  402. hm.hybrid.confirmsMu.Unlock()
  403. for _, c := range resend {
  404. if hm.l.Enabled(context.Background(), slog.LevelDebug) {
  405. c.hostinfo.logger(hm.l).Debug("Resending handshake message, responder not heard from yet", "attempts", c.attempts)
  406. }
  407. hm.sendHybridMsg3(c)
  408. }
  409. for _, c := range closing {
  410. c.hostinfo.logger(hm.l).Info("Responder never confirmed the handshake, closing tunnel",
  411. "localIndex", c.hostinfo.localIndexId, "remoteIndex", c.hostinfo.remoteIndexId)
  412. hm.f.closeTunnel(c.hostinfo)
  413. }
  414. }