-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathu_quic_frames.go
304 lines (261 loc) · 10.3 KB
/
u_quic_frames.go
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
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
package quic
import (
"bytes"
"crypto/rand"
"errors"
"math"
"math/big"
mrand "math/rand"
"github.com/refraction-networking/clienthellod"
"github.com/refraction-networking/uquic/quicvarint"
)
type QUICFrameBuilder interface {
// Build ingests data from crypto frames without the crypto frame header
// and returns the byte representation of all frames.
Build(cryptoData []byte) (allFrames []byte, err error)
}
// QUICFrames is a slice of QUICFrame that implements QUICFrameBuilder.
// It could be used to deterministically build QUIC Frames from crypto data.
type QUICFrames []QUICFrame
// Build ingests data from crypto frames without the crypto frame header
// and returns the byte representation of all frames as specified in
// the slice.
func (qfs QUICFrames) Build(cryptoData []byte) (payload []byte, err error) {
if len(qfs) == 0 { // If no frames specified, send a single crypto frame
qfsCryptoOnly := QUICFrames{QUICFrameCrypto{0, 0}}
return qfsCryptoOnly.Build(cryptoData)
}
lowestOffset := math.MaxUint16
for _, frame := range qfs {
if offset, _, _ := frame.CryptoFrameInfo(); offset < lowestOffset {
lowestOffset = offset
}
}
for _, frame := range qfs {
var frameBytes []byte
if offset, length, cryptoOK := frame.CryptoFrameInfo(); cryptoOK {
lengthOffset := offset - lowestOffset
if length == 0 {
// calculate length: from offset to the end of cryptoData
length = len(cryptoData) - lengthOffset
}
frameBytes = []byte{0x06} // CRYPTO frame type
frameBytes = quicvarint.Append(frameBytes, uint64(offset))
frameBytes = quicvarint.Append(frameBytes, uint64(length))
frameCryptoData := make([]byte, length)
copy(frameCryptoData, cryptoData[lengthOffset:]) // copy at most length bytes
frameBytes = append(frameBytes, frameCryptoData...)
} else { // Handle none crypto frames: read and append to payload
frameBytes, err = frame.Read()
if err != nil {
return nil, err
}
}
payload = append(payload, frameBytes...)
}
return payload, nil
}
// BuildFromFrames ingests data from all input frames and returns the byte representation
// of all frames as specified in the slice.
func (qfs QUICFrames) BuildFromFrames(frames []byte) (payload []byte, err error) {
// parse frames
r := bytes.NewReader(frames)
qchframes, err := clienthellod.ReadAllFrames(r)
if err != nil {
return nil, err
}
// parse crypto data
cryptoData, err := clienthellod.ReassembleCRYPTOFrames(qchframes)
if err != nil {
return nil, err
}
// marshal
return qfs.Build(cryptoData)
}
// QUICFrame is the interface for all QUIC frames to be included in the Initial Packet.
type QUICFrame interface {
// None crypto frames should return false for cryptoOK
CryptoFrameInfo() (offset, length int, cryptoOK bool)
// None crypto frames should return the byte representation of the frame.
// Crypto frames' behavior is undefined and unused.
Read() ([]byte, error)
}
// QUICFrameCrypto is used to specify the crypto frames containing the TLS ClientHello
// to be sent in the first Initial packet.
type QUICFrameCrypto struct {
// Offset is used to specify the starting offset of the crypto frame.
// Used when sending multiple crypto frames in a single packet.
//
// Multiple crypto frames in a single packet must not overlap and must
// make up an entire crypto stream continuously.
Offset int
// Length is used to specify the length of the crypto frame.
//
// Must be set if it is NOT the last crypto frame in a packet.
Length int
}
// CryptoFrameInfo() implements the QUICFrame interface.
//
// Crypto frames are later replaced by the crypto message using the information
// returned by this function.
func (q QUICFrameCrypto) CryptoFrameInfo() (offset, length int, cryptoOK bool) {
return q.Offset, q.Length, true
}
// Read() implements the QUICFrame interface.
//
// Crypto frames are later replaced by the crypto message, so they are not Read()-able.
func (q QUICFrameCrypto) Read() ([]byte, error) {
return nil, errors.New("crypto frames are not Read()-able")
}
// QUICFramePadding is used to specify the padding frames to be sent in the first Initial
// packet.
type QUICFramePadding struct {
// Length is used to specify the length of the padding frame.
Length int
}
// CryptoFrameInfo() implements the QUICFrame interface.
func (q QUICFramePadding) CryptoFrameInfo() (offset, length int, cryptoOK bool) {
return 0, 0, false
}
// Read() implements the QUICFrame interface.
//
// Padding simply returns a slice of bytes of the specified length filled with 0.
func (q QUICFramePadding) Read() ([]byte, error) {
return make([]byte, q.Length), nil
}
// QUICFramePing is used to specify the ping frames to be sent in the first Initial
// packet.
type QUICFramePing struct{}
// CryptoFrameInfo() implements the QUICFrame interface.
func (q QUICFramePing) CryptoFrameInfo() (offset, length int, cryptoOK bool) {
return 0, 0, false
}
// Read() implements the QUICFrame interface.
//
// Ping simply returns a slice of bytes of size 1 with value 0x01(PING).
func (q QUICFramePing) Read() ([]byte, error) {
return []byte{0x01}, nil
}
// QUICRandomFrames could be used to indeterministically build QUIC Frames from
// crypto data. A caller may specify how many PING and CRYPTO frames are expected
// to be included in the Initial Packet, as well as the total length plus PADDING
// frames in the end.
type QUICRandomFrames struct {
// MinPING specifies the inclusive lower bound of the number of PING frames to be
// included in the Initial Packet.
MinPING uint8
// MaxPING specifies the exclusive upper bound of the number of PING frames to be
// included in the Initial Packet. It must be at least MinPING+1.
MaxPING uint8
// MinCRYPTO specifies the inclusive lower bound of the number of CRYPTO frames to
// split the Crypto data into. It must be at least 1.
MinCRYPTO uint8
// MaxCRYPTO specifies the exclusive upper bound of the number of CRYPTO frames to
// split the Crypto data into. It must be at least MinCRYPTO+1.
MaxCRYPTO uint8
// MinPADDING specifies the inclusive lower bound of the number of PADDING frames
// to be included in the Initial Packet. It must be at least 1 if Length is not 0.
MinPADDING uint8
// MaxPADDING specifies the exclusive upper bound of the number of PADDING frames
// to be included in the Initial Packet. It must be at least MinPADDING+1 if
// Length is not 0.
MaxPADDING uint8
// Length specifies the total length of all frames including PADDING frames.
// If the Length specified is already exceeded by the CRYPTO+PING frames, no
// PADDING frames will be included.
Length uint16 // 2 bytes, max 65535
}
// Build ingests data from crypto frames without the crypto frame header
// and returns the byte representation of all frames as specified in
// the slice.
func (qrf *QUICRandomFrames) Build(cryptoData []byte) (payload []byte, err error) {
// check all bounds
if qrf.MinPING > qrf.MaxPING {
return nil, errors.New("MinPING must be less than or equal to MaxPING")
}
if qrf.MinCRYPTO < 1 {
return nil, errors.New("MinCRYPTO must be at least 1")
}
if qrf.MinCRYPTO > qrf.MaxCRYPTO {
return nil, errors.New("MinCRYPTO must be less than or equal to MaxCRYPTO")
}
if qrf.MinPADDING < 1 && qrf.Length != 0 {
return nil, errors.New("MinPADDING must be at least 1 if Length is not 0")
}
if qrf.MinPADDING > qrf.MaxPADDING && qrf.Length != 0 {
return nil, errors.New("MinPADDING must be less than or equal to MaxPADDING if Length is not 0")
}
var frameList QUICFrames = make([]QUICFrame, 0)
var cryptoSafeRandUint64 = func(min, max uint64) (uint64, error) {
minMaxDiff := big.NewInt(int64(max - min))
offset, err := rand.Int(rand.Reader, minMaxDiff)
if err != nil {
return 0, err
}
return min + offset.Uint64(), nil
}
// determine number of PING frames with crypto.rand
numPING, err := cryptoSafeRandUint64(uint64(qrf.MinPING), uint64(qrf.MaxPING))
if err != nil {
return nil, err
}
// append PING frames
for i := uint64(0); i < numPING; i++ {
frameList = append(frameList, QUICFramePing{})
}
// determine number of CRYPTO frames with crypto.rand
numCRYPTO, err := cryptoSafeRandUint64(uint64(qrf.MinCRYPTO), uint64(qrf.MaxCRYPTO))
if err != nil {
return nil, err
}
lenCryptoData := uint64(len(cryptoData))
offsetCryptoData := uint64(0)
for i := uint64(0); i < numCRYPTO-1; i++ { // select n-1 times, since the last one must be the remaining
// randomly select length of CRYPTO frame.
// Length must be at least 1 byte and at most the remaining length of cryptoData minus the remaining number of CRYPTO frames.
// i.e. len in [1, len(cryptoData)-offsetCryptoData-(numCRYPTO-i-2))
lenCRYPTO, err := cryptoSafeRandUint64(1, lenCryptoData-(numCRYPTO-i-2))
if err != nil {
return nil, err
}
frameList = append(frameList, QUICFrameCrypto{Offset: int(offsetCryptoData), Length: int(lenCRYPTO)})
offsetCryptoData += lenCRYPTO
lenCryptoData -= lenCRYPTO
}
// append the last CRYPTO frame
frameList = append(frameList, QUICFrameCrypto{Offset: int(offsetCryptoData), Length: 0}) // 0 means the remaining
// dry-run to determine the total length of all frames so far
dryrunPayload, err := frameList.Build(cryptoData)
if err != nil {
return nil, err
}
// determine length of PADDING frames to append
lenPADDINGsigned := int64(qrf.Length) - int64(len(dryrunPayload))
if lenPADDINGsigned > 0 {
lenPADDING := uint64(lenPADDINGsigned)
// determine number of PADDING frames to append
numPADDING, err := cryptoSafeRandUint64(uint64(qrf.MinPADDING), uint64(qrf.MaxPADDING))
if err != nil {
return nil, err
}
for i := uint64(0); i < numPADDING-1; i++ { // select n-1 times, since the last one must be the remaining
// randomly select length of PADDING frame.
// Length must be at least 1 byte and at most the remaining length of cryptoData minus the remaining number of CRYPTO frames.
// i.e. len in [1, lenPADDING-(numPADDING-i-2))
lenPADDINGFrame, err := cryptoSafeRandUint64(1, lenPADDING-(numPADDING-i-2))
if err != nil {
return nil, err
}
frameList = append(frameList, QUICFramePadding{Length: int(lenPADDINGFrame)})
lenPADDING -= lenPADDINGFrame
}
// append the last CRYPTO frame
frameList = append(frameList, QUICFramePadding{Length: int(lenPADDING)}) // 0 means the remaining
}
// shuffle the frameList
mrand.Shuffle(len(frameList), func(i, j int) {
frameList[i], frameList[j] = frameList[j], frameList[i]
})
// build the payload
return frameList.Build(cryptoData)
}