-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathsyn_cookie.go
More file actions
259 lines (244 loc) · 8.68 KB
/
Copy pathsyn_cookie.go
File metadata and controls
259 lines (244 loc) · 8.68 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
package mipstack
import (
"crypto/rand"
"crypto/subtle"
"encoding/binary"
"errors"
"net"
"net/netip"
"time"
)
const (
// synCookiePeriod is short enough to limit replay while allowing normal
// retransmission delays. The current and immediately previous period are
// accepted.
synCookiePeriod = 64 * time.Second
// Eight low sequence-number bits carry conservative, reconstructible peer
// options. The remaining 24 bits authenticate the tuple, client ISN, time
// period, and all negotiated option flags.
synCookieDataBits = 8
synCookieDataMask = uint32(1<<synCookieDataBits - 1)
)
// synCookieMSSValues are safe lower bounds for common advertised MSS values.
// Selecting the greatest entry no larger than the peer's offer never causes a
// reconstructed connection to transmit oversized segments.
var synCookieMSSValues = [...]uint16{tcpMinimumPeerMSS, 256, 536, 1200, 1220, 1360, 1440, 8960}
// synCookieOptions is the handshake state that can be reconstructed from a
// final ACK without retaining the original SYN.
type synCookieOptions struct {
mss int
windowScale uint8
windowScaling bool
sack bool
timestamp bool
ecn bool
timestampNow uint32
}
// key returns the lazily initialized per-stack-listener secret. A random
// source failure is retried on a later SYN instead of permanently disabling
// cookies.
func (state *tcpPassiveState) synCookieKey(now time.Time) ([16]byte, uint64, error) {
state.cookieMu.Lock()
defer state.cookieMu.Unlock()
if !state.cookieSet {
if _, err := rand.Read(state.cookieKey[:]); err != nil {
return [16]byte{}, 0, err
}
state.cookieEpoch = now
state.cookieSet = true
}
return state.cookieKey, synCookiePeriodNumber(now, state.cookieEpoch), nil
}
// noteSYNCookie records the Linux-style recent-overflow window in which a
// stateless final ACK is eligible for cookie validation.
func (state *tcpPassiveState) noteSYNCookie(period uint64) {
state.cookieMu.Lock()
if !state.cookieActive || period > state.cookiePeriod {
state.cookiePeriod = period
}
state.cookieActive = true
state.cookieMu.Unlock()
}
// recentSYNCookieKey returns an existing key only after this passive state
// actually issued a cookie in the current or preceding period.
func (state *tcpPassiveState) recentSYNCookieKey(now time.Time) ([16]byte, uint64, bool) {
state.cookieMu.Lock()
defer state.cookieMu.Unlock()
period := synCookiePeriodNumber(now, state.cookieEpoch)
if !state.cookieSet || !state.cookieActive || period < state.cookiePeriod || period-state.cookiePeriod > 1 {
return [16]byte{}, 0, false
}
return state.cookieKey, period, true
}
// sendSYNCookie replies to a SYN without allocating a TCPConn.
func (state *tcpPassiveState) sendSYNCookie(stack *Stack, key tcpKey, syn tcpSegment, now time.Time) error {
secret, period, err := state.synCookieKey(now)
if err != nil {
return err
}
options, data := encodeSYNCookieOptions(syn, key.remote.Addr())
authenticatedData := synCookieAuthenticatedData(data, options.timestamp, options.ecn)
sequence := synCookieSequence(secret, key, syn.sequence, period, data, authenticatedData)
localMSS := tcpMSSForMTU(stack.mtuFor(key.remote.Addr()), key.local.Addr())
if localMSS < 1 {
return errors.New("mipstack: MTU is too small for TCP")
}
timestamp := stack.tcpTimestamp()
if options.timestamp {
timestamp &^= 1
if options.ecn {
timestamp |= 1
}
}
tcpOptions := tcpPassiveSYNOptions(localMSS, options.sack, options.windowScaling, options.timestamp, timestamp, options.timestampNow)
flags := byte(tcpFlagSYN | tcpFlagACK)
if options.ecn {
flags |= tcpFlagECE
}
state.noteSYNCookie(period)
return stack.writeTCP(key.local.Addr(), key.remote.Addr(), key.local.Port(), key.remote.Port(), sequence, syn.sequence+1, flags, 65535, tcpOptions, nil)
}
// validateSYNCookie authenticates a final ACK against the current or previous
// time period and reconstructs its negotiated options.
func (state *tcpPassiveState) validateSYNCookie(key tcpKey, ack tcpSegment, now time.Time) (uint32, synCookieOptions, bool) {
if ack.flags&tcpFlagACK == 0 || ack.flags&(tcpFlagSYN|tcpFlagRST) != 0 {
return 0, synCookieOptions{}, false
}
secret, period, active := state.recentSYNCookieKey(now)
if !active {
return 0, synCookieOptions{}, false
}
serverSequence := ack.acknowledgement - 1
clientSequence := ack.sequence - 1
data := serverSequence & synCookieDataMask
timestampValue, timestampEcho, timestamp := parseTCPTimestamp(ack.options)
ecn := timestamp && timestampEcho&1 != 0
authenticatedData := synCookieAuthenticatedData(data, timestamp, ecn)
valid := false
for attempt := uint64(0); attempt < 2; attempt++ {
if attempt > period {
break
}
expected := synCookieSequence(secret, key, clientSequence, period-attempt, data, authenticatedData)
if subtle.ConstantTimeEq(int32(expected), int32(serverSequence)) == 1 {
valid = true
}
}
if !valid {
return 0, synCookieOptions{}, false
}
options, ok := decodeSYNCookieOptions(data)
if !ok {
return 0, synCookieOptions{}, false
}
options.timestamp = timestamp
options.timestampNow = timestampValue
options.ecn = ecn
return serverSequence, options, true
}
// encodeSYNCookieOptions converts a SYN's offered options into eight sequence
// bits. Timestamp and ECN flags are authenticated separately.
func encodeSYNCookieOptions(syn tcpSegment, remoteAddress netip.Addr) (synCookieOptions, uint32) {
mss, scale, scaling, sack, timestamp, timestampValue := parseTCPOptions(syn.options, defaultTCPPeerMSS(remoteAddress), 65535)
mssIndex := 0
for index, value := range synCookieMSSValues {
if int(value) > mss {
break
}
mssIndex = index
}
encodedScale := uint32(15)
if scaling {
encodedScale = uint32(scale)
}
data := uint32(mssIndex) | encodedScale<<3
if sack {
data |= 1 << 7
}
// The server timestamp echo carries ECN state for the final ACK. Without
// timestamps, cookie mode conservatively declines ECN rather than spending
// sequence bits that would weaken the authentication tag.
ecn := timestamp && syn.flags&(tcpFlagECE|tcpFlagCWR) == tcpFlagECE|tcpFlagCWR
return synCookieOptions{
mss: int(synCookieMSSValues[mssIndex]), windowScale: scale, windowScaling: scaling,
sack: sack, timestamp: timestamp, ecn: ecn, timestampNow: timestampValue,
}, data
}
// decodeSYNCookieOptions reconstructs peer options from authenticated data.
func decodeSYNCookieOptions(data uint32) (synCookieOptions, bool) {
if data & ^synCookieDataMask != 0 {
return synCookieOptions{}, false
}
mssIndex := int(data & 7)
encodedScale := uint8(data >> 3 & 15)
options := synCookieOptions{
mss: int(synCookieMSSValues[mssIndex]), sack: data&(1<<7) != 0,
}
if encodedScale != 15 {
options.windowScaling = true
options.windowScale = encodedScale
}
return options, true
}
// synCookieAuthenticatedData adds option flags recovered from the ACK's
// timestamp to the compact sequence data. They consume no sequence bits but
// remain covered by the keyed tag.
func synCookieAuthenticatedData(data uint32, timestamp, ecn bool) uint32 {
if timestamp {
data |= 1 << 8
}
if ecn {
data |= 1 << 9
}
return data
}
// synCookiePeriodNumber returns the monotonically advancing cookie period.
func synCookiePeriodNumber(now, epoch time.Time) uint64 {
elapsed := now.Sub(epoch)
if elapsed <= 0 {
return 0
}
return uint64(elapsed / synCookiePeriod)
}
// synCookieSequence authenticates one tuple and its compact option data.
func synCookieSequence(secret [16]byte, key tcpKey, clientSequence uint32, period uint64, data, authenticatedData uint32) uint32 {
var input [51]byte
if key.local.Addr().Is6() {
input[0] = 6
} else {
input[0] = 4
}
local := key.local.Addr().As16()
remote := key.remote.Addr().As16()
copy(input[1:17], local[:])
copy(input[17:33], remote[:])
binary.BigEndian.PutUint16(input[33:35], key.local.Port())
binary.BigEndian.PutUint16(input[35:37], key.remote.Port())
binary.BigEndian.PutUint32(input[37:41], clientSequence)
binary.BigEndian.PutUint64(input[41:49], period)
binary.BigEndian.PutUint16(input[49:51], uint16(authenticatedData))
tag := uint32(sipHash24(secret, input[:])) &^ synCookieDataMask
return tag | data
}
// runPassiveCookie owns a server-side connection reconstructed from a final
// cookie ACK.
func (c *TCPConn) runPassiveCookie(listener *TCPListener, finalACK tcpSegment, initialSequence uint32) {
queued := false
defer func() {
if !queued {
listener.removePending(c)
}
}()
defer c.stack.removeTCP(c)
defer close(c.done)
if len(finalACK.payload) != 0 || finalACK.flags&tcpFlagFIN != 0 {
c.inbound <- finalACK
}
if !listener.enqueue(c) {
_ = c.sendSegment(initialSequence+1, c.receiveNext, tcpFlagRST|tcpFlagACK, 0, nil)
c.finish(net.ErrClosed)
return
}
queued = true
c.finish(c.established(initialSequence + 1))
}