1 /*
2 * Copyright 2020 The Netty Project
3 *
4 * The Netty Project licenses this file to you under the Apache License,
5 * version 2.0 (the "License"); you may not use this file except in compliance
6 * with the License. You may obtain a copy of the License at:
7 *
8 * https://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 * License for the specific language governing permissions and limitations
14 * under the License.
15 */
16 package io.netty.handler.codec.quic;
17
18 import io.netty.buffer.ByteBuf;
19 import io.netty.buffer.Unpooled;
20
21 import java.net.InetSocketAddress;
22
23 import static io.netty.util.internal.ObjectUtil.checkPositiveOrZero;
24
25 /**
26 * Parses the QUIC packet header and notifies a callback once parsing was successful.
27 * <p>
28 * Once the parser is not needed anymore the user needs to call {@link #close()} to ensure all resources are
29 * released. Failed to do so may lead to memory leaks.
30 * <p>
31 * This class can be used for advanced use-cases. Usually you want to just use {@link QuicClientCodecBuilder} or
32 * {@link QuicServerCodecBuilder}.
33 */
34 public final class QuicHeaderParser implements AutoCloseable {
35 // See https://datatracker.ietf.org/doc/rfc7714/
36 private static final int AES_128_GCM_TAG_LENGTH = 16;
37
38 private final int localConnectionIdLength;
39 private boolean closed;
40
41 public QuicHeaderParser(int localConnectionIdLength) {
42 this.localConnectionIdLength = checkPositiveOrZero(localConnectionIdLength, "localConnectionIdLength");
43 }
44
45 @Override
46 public void close() {
47 if (!closed) {
48 closed = true;
49 }
50 }
51
52 /**
53 * Parses a QUIC packet and extract the header values out of it. This method takes no ownership of the packet itself
54 * which means the caller of this method is expected to call {@link ByteBuf#release()} once the packet is not needed
55 * anymore.
56 *
57 * @param sender the sender of the packet. This is directly passed to the {@link QuicHeaderProcessor} once
58 * parsing was successful.
59 * @param recipient the recipient of the packet.This is directly passed to the {@link QuicHeaderProcessor} once
60 * parsing was successful.
61 * @param packet raw QUIC packet itself. The ownership of the packet is not transferred. This is directly
62 * passed to the {@link QuicHeaderProcessor} once parsing was successful.
63 * @param callback the {@link QuicHeaderProcessor} that is called once a QUIC packet could be parsed and all
64 * the header values be extracted.
65 * @throws Exception thrown if we couldn't parse the header or if the {@link QuicHeaderProcessor} throws an
66 * exception.
67 */
68 public void parse(InetSocketAddress sender, InetSocketAddress recipient, ByteBuf packet,
69 QuicHeaderProcessor callback) throws Exception {
70 if (closed) {
71 throw new IllegalStateException(QuicHeaderParser.class.getSimpleName() + " is already closed");
72 }
73
74 // See https://datatracker.ietf.org/doc/html/rfc9000#section-17
75 int offset = 0;
76 int readable = packet.readableBytes();
77
78 checkReadable(offset, readable, Byte.BYTES);
79 byte first = packet.getByte(offset);
80 offset += Byte.BYTES;
81
82 final QuicPacketType type;
83 final long version;
84 final ByteBuf dcid;
85 final ByteBuf scid;
86 final ByteBuf token;
87
88 if (hasShortHeader(first)) {
89 // See https://www.rfc-editor.org/rfc/rfc9000.html#section-17.3
90 // 1-RTT Packet {
91 // Header Form (1) = 0,
92 // Fixed Bit (1) = 1,
93 // Spin Bit (1),
94 // Reserved Bits (2),
95 // Key Phase (1),
96 // Packet Number Length (2),
97 // Destination Connection ID (0..160),
98 // Packet Number (8..32),
99 // Packet Payload (8..),
100 //}
101 version = 0;
102 type = QuicPacketType.SHORT;
103 // Short packets have no source connection id and no token.
104 scid = Unpooled.EMPTY_BUFFER;
105 token = Unpooled.EMPTY_BUFFER;
106 dcid = sliceCid(packet, offset, localConnectionIdLength);
107 } else {
108 // See https://www.rfc-editor.org/rfc/rfc9000.html#section-17.2
109 // Long Header Packet {
110 // Header Form (1) = 1,
111 // Fixed Bit (1) = 1,
112 // Long Packet Type (2),
113 // Type-Specific Bits (4),
114 // Version (32),
115 // Destination Connection ID Length (8),
116 // Destination Connection ID (0..160),
117 // Source Connection ID Length (8),
118 // Source Connection ID (0..160),
119 // Type-Specific Payload (..),
120 //}
121 checkReadable(offset, readable, Integer.BYTES);
122
123 // Version uses an unsigned int:
124 // https://www.rfc-editor.org/rfc/rfc9000.html#section-15
125 version = packet.getUnsignedInt(offset);
126 offset += Integer.BYTES;
127 type = typeOfLongHeader(first, version);
128
129 checkReadable(offset, readable, Byte.BYTES);
130 int dcidLen = packet.getUnsignedByte(offset);
131 checkCidLength(dcidLen);
132 offset += Byte.BYTES;
133 dcid = sliceCid(packet, offset, dcidLen);
134 offset += dcidLen;
135
136 checkReadable(offset, readable, Byte.BYTES);
137 int scidLen = packet.getUnsignedByte(offset);
138 checkCidLength(scidLen);
139 offset += Byte.BYTES;
140 scid = sliceCid(packet, offset, scidLen);
141 offset += scidLen;
142 token = sliceToken(type, packet, offset, readable);
143 }
144 callback.process(sender, recipient, packet, type, version, scid, dcid, token);
145 }
146
147 // Check if the connection id is not longer then 20. This is what is the maximum for QUIC version 1.
148 // See https://www.rfc-editor.org/rfc/rfc9000.html#section-17.2
149 private static void checkCidLength(int length) throws QuicException {
150 if (length > Quic.MAX_CONN_ID_LEN) {
151 throw new QuicException("connection id to large: " + length + " > " + Quic.MAX_CONN_ID_LEN,
152 QuicTransportError.PROTOCOL_VIOLATION);
153 }
154 }
155
156 private static ByteBuf sliceToken(QuicPacketType type, ByteBuf packet, int offset, int readable)
157 throws QuicException {
158 switch (type) {
159 case INITIAL:
160 // See
161 checkReadable(offset, readable, Byte.BYTES);
162 int numBytes = numBytesForVariableLengthInteger(packet.getByte(offset));
163 // The token length is a variable length integer that can be up to 62 bits, so we must not narrow it
164 // to an int before we validated it against the readable bytes.
165 long len = getVariableLengthInteger(packet, offset, numBytes);
166 offset += numBytes;
167
168 checkReadable(offset, readable, len);
169 return packet.slice(offset, (int) len);
170 case RETRY:
171 // Exclude the integrity tag from the token.
172 // See https://www.rfc-editor.org/rfc/rfc9000.html#section-17.2.5
173 checkReadable(offset, readable, AES_128_GCM_TAG_LENGTH);
174 int tokenLen = readable - offset - AES_128_GCM_TAG_LENGTH;
175 return packet.slice(offset, tokenLen);
176 default:
177 // No token included.
178 return Unpooled.EMPTY_BUFFER;
179 }
180 }
181 private static QuicException newProtocolViolationException(String message) {
182 return new QuicException(message, QuicTransportError.PROTOCOL_VIOLATION);
183 }
184
185 static ByteBuf sliceCid(ByteBuf buffer, int offset, int len) throws QuicException {
186 checkReadable(offset, buffer.readableBytes(), len);
187 return buffer.slice(offset, len);
188 }
189
190 private static void checkReadable(int offset, int readable, long needed) throws QuicException {
191 int r = readable - offset;
192 if (r < needed) {
193 throw newProtocolViolationException("Not enough bytes to read, " + r + " < " + needed);
194 }
195 }
196
197 /**
198 * Read the variable length integer from the {@link ByteBuf}.
199 * See <a href="https://tools.ietf.org/html/draft-ietf-quic-transport-32#section-16">
200 * Variable-Length Integer Encoding </a>
201 */
202 private static long getVariableLengthInteger(ByteBuf in, int offset, int len) throws QuicException {
203 checkReadable(offset, in.readableBytes(), len);
204 switch (len) {
205 case 1:
206 return in.getUnsignedByte(offset);
207 case 2:
208 return in.getUnsignedShort(offset) & 0x3fff;
209 case 4:
210 return in.getUnsignedInt(offset) & 0x3fffffff;
211 case 8:
212 return in.getLong(offset) & 0x3fffffffffffffffL;
213 default:
214 throw newProtocolViolationException("Unsupported length:" + len);
215 }
216 }
217
218 /**
219 * Returns the number of bytes that were encoded into the byte for a variable length integer to read.
220 * See <a href="https://tools.ietf.org/html/draft-ietf-quic-transport-32#section-16">
221 * Variable-Length Integer Encoding </a>
222 */
223 private static int numBytesForVariableLengthInteger(byte b) {
224 byte val = (byte) (b >> 6);
225 if ((val & 1) != 0) {
226 if ((val & 2) != 0) {
227 return 8;
228 }
229 return 2;
230 }
231 if ((val & 2) != 0) {
232 return 4;
233 }
234 return 1;
235 }
236
237 static boolean hasShortHeader(byte b) {
238 return (b & 0x80) == 0;
239 }
240
241 private static QuicPacketType typeOfLongHeader(byte first, long version) throws QuicException {
242 if (version == 0) {
243 // If we parsed a version of 0 we are sure it's a version negotiation packet:
244 // https://www.rfc-editor.org/rfc/rfc9000.html#section-17.2.1
245 //
246 // This also means we should ignore everything that is left in 'first'.
247 return QuicPacketType.VERSION_NEGOTIATION;
248 }
249 int packetType = (first & 0x30) >> 4;
250 switch (packetType) {
251 case 0x00:
252 return QuicPacketType.INITIAL;
253 case 0x01:
254 return QuicPacketType.ZERO_RTT;
255 case 0x02:
256 return QuicPacketType.HANDSHAKE;
257 case 0x03:
258 return QuicPacketType.RETRY;
259 default:
260 throw newProtocolViolationException("Unknown packet type: " + packetType);
261 }
262 }
263
264 /**
265 * Called when a QUIC packet and its header could be parsed.
266 */
267 public interface QuicHeaderProcessor {
268
269 /**
270 * Called when a QUIC packet header was parsed.
271 *
272 * @param sender the sender of the QUIC packet.
273 * @param recipient the recipient of the QUIC packet.
274 * @param packet the raw QUIC packet. The ownership is not transferred, which means you will need to call
275 * {@link ByteBuf#retain()} on it if you want to keep a reference after this method
276 * returns.
277 * @param type the type of the packet.
278 * @param version the version of the packet.
279 * @param scid the source connection id. The ownership is not transferred and its generally not allowed
280 * to hold any references to this buffer outside of the method as it will be re-used.
281 * @param dcid the destination connection id. The ownership is not transferred and its generally not
282 * allowed to hold any references to this buffer outside of the method as it will be
283 * re-used.
284 * @param token the token.The ownership is not transferred and its generally not allowed
285 * to hold any references to this buffer outside of the method as it will be re-used.
286 * @throws Exception throws if an error happens during processing.
287 */
288 void process(InetSocketAddress sender, InetSocketAddress recipient, ByteBuf packet,
289 QuicPacketType type, long version, ByteBuf scid, ByteBuf dcid, ByteBuf token) throws Exception;
290 }
291 }