Skip to content

Commit 304b12f

Browse files
authored
create ConnectionState before adding to HostMap (slackhq#535)
We have a few small race conditions with creating the HostInfo.ConnectionState since we add the host info to the pendingHostMap before we set this field. We can make everything a lot easier if we just add an "init" function so that we can set this field in the hostinfo before we add it to the hostmap.
1 parent 16be0ce commit 304b12f

File tree

7 files changed

+42
-31
lines changed

7 files changed

+42
-31
lines changed

CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
5353
will immediately switch to a preferred remote address after the reception of
5454
a handshake packet (instead of waiting until 1,000 packets have been sent).
5555
(#532)
56-
56+
5757
- A race condition when `punchy.respond` is enabled and ensures the correct
5858
vpn ip is sent a punch back response in highly queried node. (#566)
5959

60+
- Fix a rare crash during handshake due to a race condition. (#535)
61+
6062
## [1.4.0] - 2021-05-11
6163

6264
### Added

connection_manager_test.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ func Test_NewConnectionManagerTest(t *testing.T) {
5757
out := make([]byte, mtu)
5858
nc.HandleMonitorTick(now, p, nb, out)
5959
// Add an ip we have established a connection w/ to hostmap
60-
hostinfo := nc.hostMap.AddVpnIp(vpnIp)
60+
hostinfo, _ := nc.hostMap.AddVpnIp(vpnIp, nil)
6161
hostinfo.ConnectionState = &ConnectionState{
6262
certState: cs,
6363
H: &noise.HandshakeState{},
@@ -126,7 +126,7 @@ func Test_NewConnectionManagerTest2(t *testing.T) {
126126
out := make([]byte, mtu)
127127
nc.HandleMonitorTick(now, p, nb, out)
128128
// Add an ip we have established a connection w/ to hostmap
129-
hostinfo := nc.hostMap.AddVpnIp(vpnIp)
129+
hostinfo, _ := nc.hostMap.AddVpnIp(vpnIp, nil)
130130
hostinfo.ConnectionState = &ConnectionState{
131131
certState: cs,
132132
H: &noise.HandshakeState{},
@@ -232,7 +232,7 @@ func Test_NewConnectionManagerTest_DisconnectInvalid(t *testing.T) {
232232
defer cancel()
233233
nc := newConnectionManager(ctx, l, ifce, 5, 10)
234234
ifce.connectionManager = nc
235-
hostinfo := nc.hostMap.AddVpnIp(vpnIp)
235+
hostinfo, _ := nc.hostMap.AddVpnIp(vpnIp, nil)
236236
hostinfo.ConnectionState = &ConnectionState{
237237
certState: cs,
238238
peerCert: &peerCert,

handshake_manager.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -191,13 +191,13 @@ func (c *HandshakeManager) handleOutbound(vpnIp iputil.VpnIp, f udp.EncWriter, l
191191
}
192192
}
193193

194-
func (c *HandshakeManager) AddVpnIp(vpnIp iputil.VpnIp) *HostInfo {
195-
hostinfo := c.pendingHostMap.AddVpnIp(vpnIp)
196-
// We lock here and use an array to insert items to prevent locking the
197-
// main receive thread for very long by waiting to add items to the pending map
198-
//TODO: what lock?
199-
c.OutboundHandshakeTimer.Add(vpnIp, c.config.tryInterval)
200-
c.metricInitiated.Inc(1)
194+
func (c *HandshakeManager) AddVpnIp(vpnIp iputil.VpnIp, init func(*HostInfo)) *HostInfo {
195+
hostinfo, created := c.pendingHostMap.AddVpnIp(vpnIp, init)
196+
197+
if created {
198+
c.OutboundHandshakeTimer.Add(vpnIp, c.config.tryInterval)
199+
c.metricInitiated.Inc(1)
200+
}
201201

202202
return hostinfo
203203
}

handshake_manager_test.go

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,19 @@ func Test_NewHandshakeManagerVpnIp(t *testing.T) {
2727
now := time.Now()
2828
blah.NextOutboundHandshakeTimerTick(now, mw)
2929

30-
i := blah.AddVpnIp(ip)
30+
var initCalled bool
31+
initFunc := func(*HostInfo) {
32+
initCalled = true
33+
}
34+
35+
i := blah.AddVpnIp(ip, initFunc)
36+
assert.True(t, initCalled)
37+
38+
initCalled = false
39+
i2 := blah.AddVpnIp(ip, initFunc)
40+
assert.False(t, initCalled)
41+
assert.Same(t, i, i2)
42+
3143
i.remotes = NewRemoteList()
3244
i.HandshakeReady = true
3345

@@ -71,7 +83,7 @@ func Test_NewHandshakeManagerTrigger(t *testing.T) {
7183

7284
assert.Equal(t, 0, testCountTimerWheelEntries(blah.OutboundHandshakeTimer))
7385

74-
hi := blah.AddVpnIp(ip)
86+
hi := blah.AddVpnIp(ip, nil)
7587
hi.HandshakeReady = true
7688
assert.Equal(t, 1, testCountTimerWheelEntries(blah.OutboundHandshakeTimer))
7789
assert.Equal(t, 0, hi.HandshakeCounter, "Should not have attempted a handshake yet")

hostmap.go

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -134,24 +134,25 @@ func (hm *HostMap) Add(ip iputil.VpnIp, hostinfo *HostInfo) {
134134
hm.Unlock()
135135
}
136136

137-
func (hm *HostMap) AddVpnIp(vpnIp iputil.VpnIp) *HostInfo {
138-
h := &HostInfo{}
137+
func (hm *HostMap) AddVpnIp(vpnIp iputil.VpnIp, init func(hostinfo *HostInfo)) (hostinfo *HostInfo, created bool) {
139138
hm.RLock()
140-
if _, ok := hm.Hosts[vpnIp]; !ok {
139+
if h, ok := hm.Hosts[vpnIp]; !ok {
141140
hm.RUnlock()
142141
h = &HostInfo{
143142
promoteCounter: 0,
144143
vpnIp: vpnIp,
145144
HandshakePacket: make(map[uint8][]byte, 0),
146145
}
146+
if init != nil {
147+
init(h)
148+
}
147149
hm.Lock()
148150
hm.Hosts[vpnIp] = h
149151
hm.Unlock()
150-
return h
152+
return h, true
151153
} else {
152-
h = hm.Hosts[vpnIp]
153154
hm.RUnlock()
154-
return h
155+
return h, false
155156
}
156157
}
157158

inside.go

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ func (f *Interface) getOrHandshake(vpnIp iputil.VpnIp) *HostInfo {
8383
if err != nil {
8484
hostinfo, err = f.handshakeManager.pendingHostMap.QueryVpnIp(vpnIp)
8585
if err != nil {
86-
hostinfo = f.handshakeManager.AddVpnIp(vpnIp)
86+
hostinfo = f.handshakeManager.AddVpnIp(vpnIp, f.initHostInfo)
8787
}
8888
}
8989
ci := hostinfo.ConnectionState
@@ -102,16 +102,6 @@ func (f *Interface) getOrHandshake(vpnIp iputil.VpnIp) *HostInfo {
102102
return hostinfo
103103
}
104104

105-
if ci == nil {
106-
// if we don't have a connection state, then send a handshake initiation
107-
ci = f.newConnectionState(f.l, true, noise.HandshakeIX, []byte{}, 0)
108-
// FIXME: Maybe make XX selectable, but probably not since psk makes it nearly pointless for us.
109-
//ci = f.newConnectionState(true, noise.HandshakeXX, []byte{}, 0)
110-
hostinfo.ConnectionState = ci
111-
} else if ci.eKey == nil {
112-
// if we don't have any state at all, create it
113-
}
114-
115105
// If we have already created the handshake packet, we don't want to call the function at all.
116106
if !hostinfo.HandshakeReady {
117107
ixHandshakeStage0(f, vpnIp, hostinfo)
@@ -131,6 +121,12 @@ func (f *Interface) getOrHandshake(vpnIp iputil.VpnIp) *HostInfo {
131121
return hostinfo
132122
}
133123

124+
// initHostInfo is the init function to pass to (*HandshakeManager).AddVpnIP that
125+
// will create the initial Noise ConnectionState
126+
func (f *Interface) initHostInfo(hostinfo *HostInfo) {
127+
hostinfo.ConnectionState = f.newConnectionState(f.l, true, noise.HandshakeIX, []byte{}, 0)
128+
}
129+
134130
func (f *Interface) sendMessageNow(t header.MessageType, st header.MessageSubType, hostInfo *HostInfo, p, nb, out []byte) {
135131
fp := &firewall.Packet{}
136132
err := newPacket(p, false, fp)

ssh.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -569,7 +569,7 @@ func sshCreateTunnel(ifce *Interface, fs interface{}, a []string, w sshd.StringW
569569
}
570570
}
571571

572-
hostInfo = ifce.handshakeManager.AddVpnIp(vpnIp)
572+
hostInfo = ifce.handshakeManager.AddVpnIp(vpnIp, ifce.initHostInfo)
573573
if addr != nil {
574574
hostInfo.SetRemote(addr)
575575
}

0 commit comments

Comments
 (0)