-
Notifications
You must be signed in to change notification settings - Fork 46
/
quic.go
132 lines (109 loc) · 4.26 KB
/
quic.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
package quicping
import (
"crypto/rand"
"encoding/binary"
"fmt"
"github.com/ooni/probe-cli/v3/internal/runtimex"
)
// buildHeader creates the unprotected QUIC header.
// https://www.rfc-editor.org/rfc/rfc9000.html#name-initial-packet
func buildHeader(destConnID, srcConnID connectionID, payloadLen int) []byte {
hdr := []byte{0xc3} // long header type, fixed
version := make([]byte, 4)
binary.BigEndian.PutUint32(version, uint32(0xbabababa))
hdr = append(hdr, version...) // version
lendID := uint8(len(destConnID))
hdr = append(hdr, lendID) // destination connection ID length
hdr = append(hdr, destConnID...) // destination connection ID
lensID := uint8(len(srcConnID))
hdr = append(hdr, lensID) // source connection ID length
hdr = append(hdr, srcConnID...) // source connection ID
hdr = append(hdr, 0x0) // token length
remainder := 4 + payloadLen
remainder_mask := 0b100000000000000
remainder_mask |= remainder
remainder_b := make([]byte, 2)
binary.BigEndian.PutUint16(remainder_b, uint16(remainder_mask))
hdr = append(hdr, remainder_b...) // remainder length: packet number + encrypted payload
pn := make([]byte, 4)
binary.BigEndian.PutUint32(pn, uint32(2))
hdr = append(hdr, pn...) // packet number
return hdr
}
// buildPacket constructs an Initial QUIC packet
// and applies Initial protection.
// https://www.rfc-editor.org/rfc/rfc9001.html#name-client-initial
func buildPacket() ([]byte, connectionID, connectionID) {
destConnID, srcConnID := generateConnectionIDs()
// generate random payload
minPayloadSize := 1200 - 14 - (len(destConnID) + len(srcConnID))
randomPayload := make([]byte, minPayloadSize)
rand.Read(randomPayload)
clientSecret, _ := computeSecrets(destConnID)
encrypted := encryptPayload(randomPayload, destConnID, clientSecret)
hdr := buildHeader(destConnID, srcConnID, len(encrypted))
raw := append(hdr, encrypted...)
raw = encryptHeader(raw, hdr, clientSecret)
return raw, destConnID, srcConnID
}
// generateConnectionID generates a connection ID using cryptographic random
func generateConnectionID(len int) connectionID {
b := make([]byte, len)
_, err := rand.Read(b)
runtimex.PanicOnError(err, "rand.Read failed")
return connectionID(b)
}
// generateConnectionIDForInitial generates a connection ID for the Initial packet.
// It uses a length randomly chosen between 8 and 18 bytes.
func generateConnectionIDForInitial() connectionID {
r := make([]byte, 1)
_, err := rand.Read(r)
runtimex.PanicOnError(err, "rand.Read failed")
len := minConnectionIDLenInitial + int(r[0])%(maxConnectionIDLen-minConnectionIDLenInitial+1)
return generateConnectionID(len)
}
// generateConnectionIDs generates a destination and source connection ID.
func generateConnectionIDs() ([]byte, []byte) {
destConnID := generateConnectionIDForInitial()
srcConnID := generateConnectionID(defaultConnectionIDLength)
return destConnID, srcConnID
}
// dissectVersionNegotiation dissects the Version Negotiation response.
// It returns the supported versions and the destination connection ID of the response,
// The destination connection ID of the response has to coincide with the source connection ID of the request.
// https://www.rfc-editor.org/rfc/rfc9000.html#name-version-negotiation-packet
func (m *Measurer) dissectVersionNegotiation(i []byte) ([]uint32, connectionID, error) {
firstByte := uint8(i[0])
mask := 0b10000000
mask &= int(firstByte)
if mask == 0 {
return nil, nil, &errUnexpectedResponse{msg: "not a long header packet"}
}
versionBytes := i[1:5]
v := binary.BigEndian.Uint32(versionBytes)
if v != 0 {
return nil, nil, &errUnexpectedResponse{msg: "unexpected Version Negotiation format"}
}
dstLength := i[5]
offset := 6 + uint8(dstLength)
dst := i[6:offset]
srcLength := i[offset]
offset = offset + 1 + srcLength
n := uint8(len(i))
var supportedVersions []uint32
for offset < n {
supportedVersions = append(supportedVersions, binary.BigEndian.Uint32(i[offset:offset+4]))
offset += 4
}
return supportedVersions, dst, nil
}
// errUnexpectedResponse is thrown when the response from the server
// is not a valid Version Negotiation packet
type errUnexpectedResponse struct {
error
msg string
}
// Error implements error.Error()
func (e *errUnexpectedResponse) Error() string {
return fmt.Sprintf("unexptected response: %s", e.msg)
}