View Javadoc
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 }