github.com/slackhq/nebula@v1.9.0/handshake_manager.go (about)

     1  package nebula
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"crypto/rand"
     7  	"encoding/binary"
     8  	"errors"
     9  	"net"
    10  	"sync"
    11  	"time"
    12  
    13  	"github.com/rcrowley/go-metrics"
    14  	"github.com/sirupsen/logrus"
    15  	"github.com/slackhq/nebula/header"
    16  	"github.com/slackhq/nebula/iputil"
    17  	"github.com/slackhq/nebula/udp"
    18  )
    19  
    20  const (
    21  	DefaultHandshakeTryInterval   = time.Millisecond * 100
    22  	DefaultHandshakeRetries       = 10
    23  	DefaultHandshakeTriggerBuffer = 64
    24  	DefaultUseRelays              = true
    25  )
    26  
    27  var (
    28  	defaultHandshakeConfig = HandshakeConfig{
    29  		tryInterval:   DefaultHandshakeTryInterval,
    30  		retries:       DefaultHandshakeRetries,
    31  		triggerBuffer: DefaultHandshakeTriggerBuffer,
    32  		useRelays:     DefaultUseRelays,
    33  	}
    34  )
    35  
    36  type HandshakeConfig struct {
    37  	tryInterval   time.Duration
    38  	retries       int
    39  	triggerBuffer int
    40  	useRelays     bool
    41  
    42  	messageMetrics *MessageMetrics
    43  }
    44  
    45  type HandshakeManager struct {
    46  	// Mutex for interacting with the vpnIps and indexes maps
    47  	sync.RWMutex
    48  
    49  	vpnIps  map[iputil.VpnIp]*HandshakeHostInfo
    50  	indexes map[uint32]*HandshakeHostInfo
    51  
    52  	mainHostMap            *HostMap
    53  	lightHouse             *LightHouse
    54  	outside                udp.Conn
    55  	config                 HandshakeConfig
    56  	OutboundHandshakeTimer *LockingTimerWheel[iputil.VpnIp]
    57  	messageMetrics         *MessageMetrics
    58  	metricInitiated        metrics.Counter
    59  	metricTimedOut         metrics.Counter
    60  	f                      *Interface
    61  	l                      *logrus.Logger
    62  
    63  	// can be used to trigger outbound handshake for the given vpnIp
    64  	trigger chan iputil.VpnIp
    65  }
    66  
    67  type HandshakeHostInfo struct {
    68  	sync.Mutex
    69  
    70  	startTime   time.Time       // Time that we first started trying with this handshake
    71  	ready       bool            // Is the handshake ready
    72  	counter     int             // How many attempts have we made so far
    73  	lastRemotes []*udp.Addr     // Remotes that we sent to during the previous attempt
    74  	packetStore []*cachedPacket // A set of packets to be transmitted once the handshake completes
    75  
    76  	hostinfo *HostInfo
    77  }
    78  
    79  func (hh *HandshakeHostInfo) cachePacket(l *logrus.Logger, t header.MessageType, st header.MessageSubType, packet []byte, f packetCallback, m *cachedPacketMetrics) {
    80  	if len(hh.packetStore) < 100 {
    81  		tempPacket := make([]byte, len(packet))
    82  		copy(tempPacket, packet)
    83  
    84  		hh.packetStore = append(hh.packetStore, &cachedPacket{t, st, f, tempPacket})
    85  		if l.Level >= logrus.DebugLevel {
    86  			hh.hostinfo.logger(l).
    87  				WithField("length", len(hh.packetStore)).
    88  				WithField("stored", true).
    89  				Debugf("Packet store")
    90  		}
    91  
    92  	} else {
    93  		m.dropped.Inc(1)
    94  
    95  		if l.Level >= logrus.DebugLevel {
    96  			hh.hostinfo.logger(l).
    97  				WithField("length", len(hh.packetStore)).
    98  				WithField("stored", false).
    99  				Debugf("Packet store")
   100  		}
   101  	}
   102  }
   103  
   104  func NewHandshakeManager(l *logrus.Logger, mainHostMap *HostMap, lightHouse *LightHouse, outside udp.Conn, config HandshakeConfig) *HandshakeManager {
   105  	return &HandshakeManager{
   106  		vpnIps:                 map[iputil.VpnIp]*HandshakeHostInfo{},
   107  		indexes:                map[uint32]*HandshakeHostInfo{},
   108  		mainHostMap:            mainHostMap,
   109  		lightHouse:             lightHouse,
   110  		outside:                outside,
   111  		config:                 config,
   112  		trigger:                make(chan iputil.VpnIp, config.triggerBuffer),
   113  		OutboundHandshakeTimer: NewLockingTimerWheel[iputil.VpnIp](config.tryInterval, hsTimeout(config.retries, config.tryInterval)),
   114  		messageMetrics:         config.messageMetrics,
   115  		metricInitiated:        metrics.GetOrRegisterCounter("handshake_manager.initiated", nil),
   116  		metricTimedOut:         metrics.GetOrRegisterCounter("handshake_manager.timed_out", nil),
   117  		l:                      l,
   118  	}
   119  }
   120  
   121  func (c *HandshakeManager) Run(ctx context.Context) {
   122  	clockSource := time.NewTicker(c.config.tryInterval)
   123  	defer clockSource.Stop()
   124  
   125  	for {
   126  		select {
   127  		case <-ctx.Done():
   128  			return
   129  		case vpnIP := <-c.trigger:
   130  			c.handleOutbound(vpnIP, true)
   131  		case now := <-clockSource.C:
   132  			c.NextOutboundHandshakeTimerTick(now)
   133  		}
   134  	}
   135  }
   136  
   137  func (hm *HandshakeManager) HandleIncoming(addr *udp.Addr, via *ViaSender, packet []byte, h *header.H) {
   138  	// First remote allow list check before we know the vpnIp
   139  	if addr != nil {
   140  		if !hm.lightHouse.GetRemoteAllowList().AllowUnknownVpnIp(addr.IP) {
   141  			hm.l.WithField("udpAddr", addr).Debug("lighthouse.remote_allow_list denied incoming handshake")
   142  			return
   143  		}
   144  	}
   145  
   146  	switch h.Subtype {
   147  	case header.HandshakeIXPSK0:
   148  		switch h.MessageCounter {
   149  		case 1:
   150  			ixHandshakeStage1(hm.f, addr, via, packet, h)
   151  
   152  		case 2:
   153  			newHostinfo := hm.queryIndex(h.RemoteIndex)
   154  			tearDown := ixHandshakeStage2(hm.f, addr, via, newHostinfo, packet, h)
   155  			if tearDown && newHostinfo != nil {
   156  				hm.DeleteHostInfo(newHostinfo.hostinfo)
   157  			}
   158  		}
   159  	}
   160  }
   161  
   162  func (c *HandshakeManager) NextOutboundHandshakeTimerTick(now time.Time) {
   163  	c.OutboundHandshakeTimer.Advance(now)
   164  	for {
   165  		vpnIp, has := c.OutboundHandshakeTimer.Purge()
   166  		if !has {
   167  			break
   168  		}
   169  		c.handleOutbound(vpnIp, false)
   170  	}
   171  }
   172  
   173  func (hm *HandshakeManager) handleOutbound(vpnIp iputil.VpnIp, lighthouseTriggered bool) {
   174  	hh := hm.queryVpnIp(vpnIp)
   175  	if hh == nil {
   176  		return
   177  	}
   178  	hh.Lock()
   179  	defer hh.Unlock()
   180  
   181  	hostinfo := hh.hostinfo
   182  	// If we are out of time, clean up
   183  	if hh.counter >= hm.config.retries {
   184  		hh.hostinfo.logger(hm.l).WithField("udpAddrs", hh.hostinfo.remotes.CopyAddrs(hm.mainHostMap.GetPreferredRanges())).
   185  			WithField("initiatorIndex", hh.hostinfo.localIndexId).
   186  			WithField("remoteIndex", hh.hostinfo.remoteIndexId).
   187  			WithField("handshake", m{"stage": 1, "style": "ix_psk0"}).
   188  			WithField("durationNs", time.Since(hh.startTime).Nanoseconds()).
   189  			Info("Handshake timed out")
   190  		hm.metricTimedOut.Inc(1)
   191  		hm.DeleteHostInfo(hostinfo)
   192  		return
   193  	}
   194  
   195  	// Increment the counter to increase our delay, linear backoff
   196  	hh.counter++
   197  
   198  	// Check if we have a handshake packet to transmit yet
   199  	if !hh.ready {
   200  		if !ixHandshakeStage0(hm.f, hh) {
   201  			hm.OutboundHandshakeTimer.Add(vpnIp, hm.config.tryInterval*time.Duration(hh.counter))
   202  			return
   203  		}
   204  	}
   205  
   206  	// Get a remotes object if we don't already have one.
   207  	// This is mainly to protect us as this should never be the case
   208  	// NB ^ This comment doesn't jive. It's how the thing gets initialized.
   209  	// It's the common path. Should it update every time, in case a future LH query/queries give us more info?
   210  	if hostinfo.remotes == nil {
   211  		hostinfo.remotes = hm.lightHouse.QueryCache(vpnIp)
   212  	}
   213  
   214  	remotes := hostinfo.remotes.CopyAddrs(hm.mainHostMap.GetPreferredRanges())
   215  	remotesHaveChanged := !udp.AddrSlice(remotes).Equal(hh.lastRemotes)
   216  
   217  	// We only care about a lighthouse trigger if we have new remotes to send to.
   218  	// This is a very specific optimization for a fast lighthouse reply.
   219  	if lighthouseTriggered && !remotesHaveChanged {
   220  		// If we didn't return here a lighthouse could cause us to aggressively send handshakes
   221  		return
   222  	}
   223  
   224  	hh.lastRemotes = remotes
   225  
   226  	// TODO: this will generate a load of queries for hosts with only 1 ip
   227  	// (such as ones registered to the lighthouse with only a private IP)
   228  	// So we only do it one time after attempting 5 handshakes already.
   229  	if len(remotes) <= 1 && hh.counter == 5 {
   230  		// If we only have 1 remote it is highly likely our query raced with the other host registered within the lighthouse
   231  		// Our vpnIp here has a tunnel with a lighthouse but has yet to send a host update packet there so we only know about
   232  		// the learned public ip for them. Query again to short circuit the promotion counter
   233  		hm.lightHouse.QueryServer(vpnIp)
   234  	}
   235  
   236  	// Send the handshake to all known ips, stage 2 takes care of assigning the hostinfo.remote based on the first to reply
   237  	var sentTo []*udp.Addr
   238  	hostinfo.remotes.ForEach(hm.mainHostMap.GetPreferredRanges(), func(addr *udp.Addr, _ bool) {
   239  		hm.messageMetrics.Tx(header.Handshake, header.MessageSubType(hostinfo.HandshakePacket[0][1]), 1)
   240  		err := hm.outside.WriteTo(hostinfo.HandshakePacket[0], addr)
   241  		if err != nil {
   242  			hostinfo.logger(hm.l).WithField("udpAddr", addr).
   243  				WithField("initiatorIndex", hostinfo.localIndexId).
   244  				WithField("handshake", m{"stage": 1, "style": "ix_psk0"}).
   245  				WithError(err).Error("Failed to send handshake message")
   246  
   247  		} else {
   248  			sentTo = append(sentTo, addr)
   249  		}
   250  	})
   251  
   252  	// Don't be too noisy or confusing if we fail to send a handshake - if we don't get through we'll eventually log a timeout,
   253  	// so only log when the list of remotes has changed
   254  	if remotesHaveChanged {
   255  		hostinfo.logger(hm.l).WithField("udpAddrs", sentTo).
   256  			WithField("initiatorIndex", hostinfo.localIndexId).
   257  			WithField("handshake", m{"stage": 1, "style": "ix_psk0"}).
   258  			Info("Handshake message sent")
   259  	} else if hm.l.IsLevelEnabled(logrus.DebugLevel) {
   260  		hostinfo.logger(hm.l).WithField("udpAddrs", sentTo).
   261  			WithField("initiatorIndex", hostinfo.localIndexId).
   262  			WithField("handshake", m{"stage": 1, "style": "ix_psk0"}).
   263  			Debug("Handshake message sent")
   264  	}
   265  
   266  	if hm.config.useRelays && len(hostinfo.remotes.relays) > 0 {
   267  		hostinfo.logger(hm.l).WithField("relays", hostinfo.remotes.relays).Info("Attempt to relay through hosts")
   268  		// Send a RelayRequest to all known Relay IP's
   269  		for _, relay := range hostinfo.remotes.relays {
   270  			// Don't relay to myself, and don't relay through the host I'm trying to connect to
   271  			if *relay == vpnIp || *relay == hm.lightHouse.myVpnIp {
   272  				continue
   273  			}
   274  			relayHostInfo := hm.mainHostMap.QueryVpnIp(*relay)
   275  			if relayHostInfo == nil || relayHostInfo.remote == nil {
   276  				hostinfo.logger(hm.l).WithField("relay", relay.String()).Info("Establish tunnel to relay target")
   277  				hm.f.Handshake(*relay)
   278  				continue
   279  			}
   280  			// Check the relay HostInfo to see if we already established a relay through it
   281  			if existingRelay, ok := relayHostInfo.relayState.QueryRelayForByIp(vpnIp); ok {
   282  				switch existingRelay.State {
   283  				case Established:
   284  					hostinfo.logger(hm.l).WithField("relay", relay.String()).Info("Send handshake via relay")
   285  					hm.f.SendVia(relayHostInfo, existingRelay, hostinfo.HandshakePacket[0], make([]byte, 12), make([]byte, mtu), false)
   286  				case Requested:
   287  					hostinfo.logger(hm.l).WithField("relay", relay.String()).Info("Re-send CreateRelay request")
   288  					// Re-send the CreateRelay request, in case the previous one was lost.
   289  					m := NebulaControl{
   290  						Type:                NebulaControl_CreateRelayRequest,
   291  						InitiatorRelayIndex: existingRelay.LocalIndex,
   292  						RelayFromIp:         uint32(hm.lightHouse.myVpnIp),
   293  						RelayToIp:           uint32(vpnIp),
   294  					}
   295  					msg, err := m.Marshal()
   296  					if err != nil {
   297  						hostinfo.logger(hm.l).
   298  							WithError(err).
   299  							Error("Failed to marshal Control message to create relay")
   300  					} else {
   301  						// This must send over the hostinfo, not over hm.Hosts[ip]
   302  						hm.f.SendMessageToHostInfo(header.Control, 0, relayHostInfo, msg, make([]byte, 12), make([]byte, mtu))
   303  						hm.l.WithFields(logrus.Fields{
   304  							"relayFrom":           hm.lightHouse.myVpnIp,
   305  							"relayTo":             vpnIp,
   306  							"initiatorRelayIndex": existingRelay.LocalIndex,
   307  							"relay":               *relay}).
   308  							Info("send CreateRelayRequest")
   309  					}
   310  				default:
   311  					hostinfo.logger(hm.l).
   312  						WithField("vpnIp", vpnIp).
   313  						WithField("state", existingRelay.State).
   314  						WithField("relay", relayHostInfo.vpnIp).
   315  						Errorf("Relay unexpected state")
   316  				}
   317  			} else {
   318  				// No relays exist or requested yet.
   319  				if relayHostInfo.remote != nil {
   320  					idx, err := AddRelay(hm.l, relayHostInfo, hm.mainHostMap, vpnIp, nil, TerminalType, Requested)
   321  					if err != nil {
   322  						hostinfo.logger(hm.l).WithField("relay", relay.String()).WithError(err).Info("Failed to add relay to hostmap")
   323  					}
   324  
   325  					m := NebulaControl{
   326  						Type:                NebulaControl_CreateRelayRequest,
   327  						InitiatorRelayIndex: idx,
   328  						RelayFromIp:         uint32(hm.lightHouse.myVpnIp),
   329  						RelayToIp:           uint32(vpnIp),
   330  					}
   331  					msg, err := m.Marshal()
   332  					if err != nil {
   333  						hostinfo.logger(hm.l).
   334  							WithError(err).
   335  							Error("Failed to marshal Control message to create relay")
   336  					} else {
   337  						hm.f.SendMessageToHostInfo(header.Control, 0, relayHostInfo, msg, make([]byte, 12), make([]byte, mtu))
   338  						hm.l.WithFields(logrus.Fields{
   339  							"relayFrom":           hm.lightHouse.myVpnIp,
   340  							"relayTo":             vpnIp,
   341  							"initiatorRelayIndex": idx,
   342  							"relay":               *relay}).
   343  							Info("send CreateRelayRequest")
   344  					}
   345  				}
   346  			}
   347  		}
   348  	}
   349  
   350  	// If a lighthouse triggered this attempt then we are still in the timer wheel and do not need to re-add
   351  	if !lighthouseTriggered {
   352  		hm.OutboundHandshakeTimer.Add(vpnIp, hm.config.tryInterval*time.Duration(hh.counter))
   353  	}
   354  }
   355  
   356  // GetOrHandshake will try to find a hostinfo with a fully formed tunnel or start a new handshake if one is not present
   357  // The 2nd argument will be true if the hostinfo is ready to transmit traffic
   358  func (hm *HandshakeManager) GetOrHandshake(vpnIp iputil.VpnIp, cacheCb func(*HandshakeHostInfo)) (*HostInfo, bool) {
   359  	// Check the main hostmap and maintain a read lock if our host is not there
   360  	hm.mainHostMap.RLock()
   361  	if h, ok := hm.mainHostMap.Hosts[vpnIp]; ok {
   362  		hm.mainHostMap.RUnlock()
   363  		// Do not attempt promotion if you are a lighthouse
   364  		if !hm.lightHouse.amLighthouse {
   365  			h.TryPromoteBest(hm.mainHostMap.GetPreferredRanges(), hm.f)
   366  		}
   367  		return h, true
   368  	}
   369  
   370  	defer hm.mainHostMap.RUnlock()
   371  	return hm.StartHandshake(vpnIp, cacheCb), false
   372  }
   373  
   374  // StartHandshake will ensure a handshake is currently being attempted for the provided vpn ip
   375  func (hm *HandshakeManager) StartHandshake(vpnIp iputil.VpnIp, cacheCb func(*HandshakeHostInfo)) *HostInfo {
   376  	hm.Lock()
   377  
   378  	if hh, ok := hm.vpnIps[vpnIp]; ok {
   379  		// We are already trying to handshake with this vpn ip
   380  		if cacheCb != nil {
   381  			cacheCb(hh)
   382  		}
   383  		hm.Unlock()
   384  		return hh.hostinfo
   385  	}
   386  
   387  	hostinfo := &HostInfo{
   388  		vpnIp:           vpnIp,
   389  		HandshakePacket: make(map[uint8][]byte, 0),
   390  		relayState: RelayState{
   391  			relays:        map[iputil.VpnIp]struct{}{},
   392  			relayForByIp:  map[iputil.VpnIp]*Relay{},
   393  			relayForByIdx: map[uint32]*Relay{},
   394  		},
   395  	}
   396  
   397  	hh := &HandshakeHostInfo{
   398  		hostinfo:  hostinfo,
   399  		startTime: time.Now(),
   400  	}
   401  	hm.vpnIps[vpnIp] = hh
   402  	hm.metricInitiated.Inc(1)
   403  	hm.OutboundHandshakeTimer.Add(vpnIp, hm.config.tryInterval)
   404  
   405  	if cacheCb != nil {
   406  		cacheCb(hh)
   407  	}
   408  
   409  	// If this is a static host, we don't need to wait for the HostQueryReply
   410  	// We can trigger the handshake right now
   411  	_, doTrigger := hm.lightHouse.GetStaticHostList()[vpnIp]
   412  	if !doTrigger {
   413  		// Add any calculated remotes, and trigger early handshake if one found
   414  		doTrigger = hm.lightHouse.addCalculatedRemotes(vpnIp)
   415  	}
   416  
   417  	if doTrigger {
   418  		select {
   419  		case hm.trigger <- vpnIp:
   420  		default:
   421  		}
   422  	}
   423  
   424  	hm.Unlock()
   425  	hm.lightHouse.QueryServer(vpnIp)
   426  	return hostinfo
   427  }
   428  
   429  var (
   430  	ErrExistingHostInfo    = errors.New("existing hostinfo")
   431  	ErrAlreadySeen         = errors.New("already seen")
   432  	ErrLocalIndexCollision = errors.New("local index collision")
   433  )
   434  
   435  // CheckAndComplete checks for any conflicts in the main and pending hostmap
   436  // before adding hostinfo to main. If err is nil, it was added. Otherwise err will be:
   437  //
   438  // ErrAlreadySeen if we already have an entry in the hostmap that has seen the
   439  // exact same handshake packet
   440  //
   441  // ErrExistingHostInfo if we already have an entry in the hostmap for this
   442  // VpnIp and the new handshake was older than the one we currently have
   443  //
   444  // ErrLocalIndexCollision if we already have an entry in the main or pending
   445  // hostmap for the hostinfo.localIndexId.
   446  func (c *HandshakeManager) CheckAndComplete(hostinfo *HostInfo, handshakePacket uint8, f *Interface) (*HostInfo, error) {
   447  	c.mainHostMap.Lock()
   448  	defer c.mainHostMap.Unlock()
   449  	c.Lock()
   450  	defer c.Unlock()
   451  
   452  	// Check if we already have a tunnel with this vpn ip
   453  	existingHostInfo, found := c.mainHostMap.Hosts[hostinfo.vpnIp]
   454  	if found && existingHostInfo != nil {
   455  		testHostInfo := existingHostInfo
   456  		for testHostInfo != nil {
   457  			// Is it just a delayed handshake packet?
   458  			if bytes.Equal(hostinfo.HandshakePacket[handshakePacket], testHostInfo.HandshakePacket[handshakePacket]) {
   459  				return testHostInfo, ErrAlreadySeen
   460  			}
   461  
   462  			testHostInfo = testHostInfo.next
   463  		}
   464  
   465  		// Is this a newer handshake?
   466  		if existingHostInfo.lastHandshakeTime >= hostinfo.lastHandshakeTime && !existingHostInfo.ConnectionState.initiator {
   467  			return existingHostInfo, ErrExistingHostInfo
   468  		}
   469  
   470  		existingHostInfo.logger(c.l).Info("Taking new handshake")
   471  	}
   472  
   473  	existingIndex, found := c.mainHostMap.Indexes[hostinfo.localIndexId]
   474  	if found {
   475  		// We have a collision, but for a different hostinfo
   476  		return existingIndex, ErrLocalIndexCollision
   477  	}
   478  
   479  	existingPendingIndex, found := c.indexes[hostinfo.localIndexId]
   480  	if found && existingPendingIndex.hostinfo != hostinfo {
   481  		// We have a collision, but for a different hostinfo
   482  		return existingIndex, ErrLocalIndexCollision
   483  	}
   484  
   485  	existingRemoteIndex, found := c.mainHostMap.RemoteIndexes[hostinfo.remoteIndexId]
   486  	if found && existingRemoteIndex != nil && existingRemoteIndex.vpnIp != hostinfo.vpnIp {
   487  		// We have a collision, but this can happen since we can't control
   488  		// the remote ID. Just log about the situation as a note.
   489  		hostinfo.logger(c.l).
   490  			WithField("remoteIndex", hostinfo.remoteIndexId).WithField("collision", existingRemoteIndex.vpnIp).
   491  			Info("New host shadows existing host remoteIndex")
   492  	}
   493  
   494  	c.mainHostMap.unlockedAddHostInfo(hostinfo, f)
   495  	return existingHostInfo, nil
   496  }
   497  
   498  // Complete is a simpler version of CheckAndComplete when we already know we
   499  // won't have a localIndexId collision because we already have an entry in the
   500  // pendingHostMap. An existing hostinfo is returned if there was one.
   501  func (hm *HandshakeManager) Complete(hostinfo *HostInfo, f *Interface) {
   502  	hm.mainHostMap.Lock()
   503  	defer hm.mainHostMap.Unlock()
   504  	hm.Lock()
   505  	defer hm.Unlock()
   506  
   507  	existingRemoteIndex, found := hm.mainHostMap.RemoteIndexes[hostinfo.remoteIndexId]
   508  	if found && existingRemoteIndex != nil {
   509  		// We have a collision, but this can happen since we can't control
   510  		// the remote ID. Just log about the situation as a note.
   511  		hostinfo.logger(hm.l).
   512  			WithField("remoteIndex", hostinfo.remoteIndexId).WithField("collision", existingRemoteIndex.vpnIp).
   513  			Info("New host shadows existing host remoteIndex")
   514  	}
   515  
   516  	// We need to remove from the pending hostmap first to avoid undoing work when after to the main hostmap.
   517  	hm.unlockedDeleteHostInfo(hostinfo)
   518  	hm.mainHostMap.unlockedAddHostInfo(hostinfo, f)
   519  }
   520  
   521  // allocateIndex generates a unique localIndexId for this HostInfo
   522  // and adds it to the pendingHostMap. Will error if we are unable to generate
   523  // a unique localIndexId
   524  func (hm *HandshakeManager) allocateIndex(hh *HandshakeHostInfo) error {
   525  	hm.mainHostMap.RLock()
   526  	defer hm.mainHostMap.RUnlock()
   527  	hm.Lock()
   528  	defer hm.Unlock()
   529  
   530  	for i := 0; i < 32; i++ {
   531  		index, err := generateIndex(hm.l)
   532  		if err != nil {
   533  			return err
   534  		}
   535  
   536  		_, inPending := hm.indexes[index]
   537  		_, inMain := hm.mainHostMap.Indexes[index]
   538  
   539  		if !inMain && !inPending {
   540  			hh.hostinfo.localIndexId = index
   541  			hm.indexes[index] = hh
   542  			return nil
   543  		}
   544  	}
   545  
   546  	return errors.New("failed to generate unique localIndexId")
   547  }
   548  
   549  func (c *HandshakeManager) DeleteHostInfo(hostinfo *HostInfo) {
   550  	c.Lock()
   551  	defer c.Unlock()
   552  	c.unlockedDeleteHostInfo(hostinfo)
   553  }
   554  
   555  func (c *HandshakeManager) unlockedDeleteHostInfo(hostinfo *HostInfo) {
   556  	delete(c.vpnIps, hostinfo.vpnIp)
   557  	if len(c.vpnIps) == 0 {
   558  		c.vpnIps = map[iputil.VpnIp]*HandshakeHostInfo{}
   559  	}
   560  
   561  	delete(c.indexes, hostinfo.localIndexId)
   562  	if len(c.vpnIps) == 0 {
   563  		c.indexes = map[uint32]*HandshakeHostInfo{}
   564  	}
   565  
   566  	if c.l.Level >= logrus.DebugLevel {
   567  		c.l.WithField("hostMap", m{"mapTotalSize": len(c.vpnIps),
   568  			"vpnIp": hostinfo.vpnIp, "indexNumber": hostinfo.localIndexId, "remoteIndexNumber": hostinfo.remoteIndexId}).
   569  			Debug("Pending hostmap hostInfo deleted")
   570  	}
   571  }
   572  
   573  func (hm *HandshakeManager) QueryVpnIp(vpnIp iputil.VpnIp) *HostInfo {
   574  	hh := hm.queryVpnIp(vpnIp)
   575  	if hh != nil {
   576  		return hh.hostinfo
   577  	}
   578  	return nil
   579  
   580  }
   581  
   582  func (hm *HandshakeManager) queryVpnIp(vpnIp iputil.VpnIp) *HandshakeHostInfo {
   583  	hm.RLock()
   584  	defer hm.RUnlock()
   585  	return hm.vpnIps[vpnIp]
   586  }
   587  
   588  func (hm *HandshakeManager) QueryIndex(index uint32) *HostInfo {
   589  	hh := hm.queryIndex(index)
   590  	if hh != nil {
   591  		return hh.hostinfo
   592  	}
   593  	return nil
   594  }
   595  
   596  func (hm *HandshakeManager) queryIndex(index uint32) *HandshakeHostInfo {
   597  	hm.RLock()
   598  	defer hm.RUnlock()
   599  	return hm.indexes[index]
   600  }
   601  
   602  func (c *HandshakeManager) GetPreferredRanges() []*net.IPNet {
   603  	return c.mainHostMap.GetPreferredRanges()
   604  }
   605  
   606  func (c *HandshakeManager) ForEachVpnIp(f controlEach) {
   607  	c.RLock()
   608  	defer c.RUnlock()
   609  
   610  	for _, v := range c.vpnIps {
   611  		f(v.hostinfo)
   612  	}
   613  }
   614  
   615  func (c *HandshakeManager) ForEachIndex(f controlEach) {
   616  	c.RLock()
   617  	defer c.RUnlock()
   618  
   619  	for _, v := range c.indexes {
   620  		f(v.hostinfo)
   621  	}
   622  }
   623  
   624  func (c *HandshakeManager) EmitStats() {
   625  	c.RLock()
   626  	hostLen := len(c.vpnIps)
   627  	indexLen := len(c.indexes)
   628  	c.RUnlock()
   629  
   630  	metrics.GetOrRegisterGauge("hostmap.pending.hosts", nil).Update(int64(hostLen))
   631  	metrics.GetOrRegisterGauge("hostmap.pending.indexes", nil).Update(int64(indexLen))
   632  	c.mainHostMap.EmitStats()
   633  }
   634  
   635  // Utility functions below
   636  
   637  func generateIndex(l *logrus.Logger) (uint32, error) {
   638  	b := make([]byte, 4)
   639  
   640  	// Let zero mean we don't know the ID, so don't generate zero
   641  	var index uint32
   642  	for index == 0 {
   643  		_, err := rand.Read(b)
   644  		if err != nil {
   645  			l.Errorln(err)
   646  			return 0, err
   647  		}
   648  
   649  		index = binary.BigEndian.Uint32(b)
   650  	}
   651  
   652  	if l.Level >= logrus.DebugLevel {
   653  		l.WithField("index", index).
   654  			Debug("Generated index")
   655  	}
   656  	return index, nil
   657  }
   658  
   659  func hsTimeout(tries int, interval time.Duration) time.Duration {
   660  	return time.Duration(tries / 2 * ((2 * int(interval)) + (tries-1)*int(interval)))
   661  }