View Javadoc
1   /*
2    * Copyright 2021 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.util.ReferenceCounted;
20  import io.netty.util.ResourceLeakDetector;
21  import io.netty.util.ResourceLeakDetectorFactory;
22  import io.netty.util.ResourceLeakTracker;
23  import org.jetbrains.annotations.Nullable;
24  
25  import java.net.InetSocketAddress;
26  import java.nio.ByteBuffer;
27  import java.util.function.Consumer;
28  import java.util.function.Supplier;
29  
30  final class QuicheQuicConnection {
31      private static final int TOTAL_RECV_INFO_SIZE = Quiche.SIZEOF_QUICHE_RECV_INFO +
32              Quiche.SIZEOF_SOCKADDR_STORAGE + Quiche.SIZEOF_SOCKADDR_STORAGE;
33      private static final ResourceLeakDetector<QuicheQuicConnection> leakDetector =
34              ResourceLeakDetectorFactory.instance().newResourceLeakDetector(QuicheQuicConnection.class);
35      private final QuicheQuicSslEngine engine;
36  
37      private final ResourceLeakTracker<QuicheQuicConnection> leakTracker;
38  
39      final long ssl;
40      private ReferenceCounted refCnt;
41  
42      // This block of memory is used to store the following structs (in this order):
43      // - quiche_recv_info
44      // - sockaddr_storage
45      // - quiche_recv_info
46      // - sockaddr_storage
47      // - quiche_send_info
48      // - quiche_send_info
49      //
50      // We need to have every stored 2 times as we need to check if the last sockaddr has changed between
51      // quiche_conn_recv and quiche_conn_send calls. If this happens we know a QUIC connection migration did happen.
52      private final ByteBuf recvInfoBuffer;
53      private final ByteBuf sendInfoBuffer;
54  
55      private boolean sendInfoFirst = true;
56      private final ByteBuffer recvInfoBuffer1;
57      private final ByteBuffer sendInfoBuffer1;
58      private final ByteBuffer sendInfoBuffer2;
59  
60      private long connection;
61  
62      QuicheQuicConnection(long connection, long ssl, QuicheQuicSslEngine engine, ReferenceCounted refCnt) {
63          this.connection = connection;
64          this.ssl = ssl;
65          this.engine = engine;
66          this.refCnt = refCnt;
67          // TODO: Maybe cache these per thread as we only use them temporary within a limited scope.
68          recvInfoBuffer = Quiche.allocateNativeOrder(TOTAL_RECV_INFO_SIZE);
69          sendInfoBuffer = Quiche.allocateNativeOrder(2 * Quiche.SIZEOF_QUICHE_SEND_INFO);
70  
71          // Let's memset the memory.
72          recvInfoBuffer.setZero(0, recvInfoBuffer.capacity());
73          sendInfoBuffer.setZero(0, sendInfoBuffer.capacity());
74  
75          recvInfoBuffer1 = recvInfoBuffer.nioBuffer(0, TOTAL_RECV_INFO_SIZE);
76          sendInfoBuffer1 = sendInfoBuffer.nioBuffer(0, Quiche.SIZEOF_QUICHE_SEND_INFO);
77          sendInfoBuffer2 = sendInfoBuffer.nioBuffer(Quiche.SIZEOF_QUICHE_SEND_INFO, Quiche.SIZEOF_QUICHE_SEND_INFO);
78          this.engine.connection = this;
79          leakTracker = leakDetector.track(this);
80      }
81  
82      synchronized void reattach(ReferenceCounted refCnt) {
83          this.refCnt.release();
84          this.refCnt = refCnt;
85      }
86  
87      void free() {
88          free(true);
89      }
90  
91      boolean isFreed() {
92          return connection == -1;
93      }
94  
95      private void free(boolean closeLeakTracker) {
96          boolean release = false;
97          synchronized (this) {
98              if (connection != -1) {
99                  try {
100                     BoringSSL.SSL_cleanup(ssl);
101                     Quiche.quiche_conn_free(connection);
102                     engine.ctx.remove(engine);
103                     release = true;
104                     refCnt.release();
105                 } finally {
106                     connection = -1;
107                 }
108             }
109         }
110         if (release) {
111             recvInfoBuffer.release();
112             sendInfoBuffer.release();
113             if (closeLeakTracker && leakTracker != null) {
114                 leakTracker.close(this);
115             }
116         }
117     }
118 
119     @Nullable
120     Runnable sslTask() {
121         final Runnable task;
122         synchronized (this) {
123             if (connection != -1) {
124                 task = BoringSSL.SSL_getTask(ssl);
125             } else {
126                 task = null;
127             }
128         }
129         if (task == null) {
130             return null;
131         }
132 
133         return () -> {
134             if (connection == -1) {
135                 return;
136             }
137 
138             task.run();
139         };
140     }
141 
142     @Nullable
143     QuicConnectionAddress sourceId() {
144         return connectionId(() -> Quiche.quiche_conn_source_id(connection));
145     }
146 
147     @Nullable
148     QuicConnectionAddress destinationId() {
149         return connectionId(() -> Quiche.quiche_conn_destination_id(connection));
150     }
151 
152     @Nullable
153     QuicConnectionAddress connectionId(Supplier<byte[]> idSupplier) {
154         final byte[] id;
155         synchronized (this) {
156             if (connection == -1) {
157                 return null;
158             }
159             id = idSupplier.get();
160         }
161         return id == null ? QuicConnectionAddress.NULL_LEN : new QuicConnectionAddress(id);
162     }
163 
164     @Nullable
165     QuicheQuicTransportParameters peerParameters() {
166         final long[] ret;
167         synchronized (this) {
168             if (connection == -1) {
169                 return null;
170             }
171             ret = Quiche.quiche_conn_peer_transport_params(connection);
172         }
173         if (ret == null) {
174             return null;
175         }
176         return new QuicheQuicTransportParameters(ret);
177     }
178 
179     QuicheQuicSslEngine engine() {
180         return engine;
181     }
182 
183     long address() {
184         assert connection != -1;
185         return connection;
186     }
187 
188     void init(InetSocketAddress local, InetSocketAddress remote, Consumer<String> sniSelectedCallback) {
189         assert connection != -1;
190         assert recvInfoBuffer.refCnt() != 0;
191         assert sendInfoBuffer.refCnt() != 0;
192 
193         // Fill quiche_recv_info struct with the addresses.
194         QuicheRecvInfo.setRecvInfo(recvInfoBuffer1, remote, local);
195 
196         // Fill both quiche_send_info structs with the same addresses.
197         QuicheSendInfo.setSendInfo(sendInfoBuffer1, local, remote);
198         QuicheSendInfo.setSendInfo(sendInfoBuffer2, local, remote);
199         engine.sniSelectedCallback = sniSelectedCallback;
200     }
201 
202     ByteBuffer nextRecvInfo() {
203         assert recvInfoBuffer.refCnt() != 0;
204         return recvInfoBuffer1;
205     }
206 
207     ByteBuffer nextSendInfo() {
208         assert sendInfoBuffer.refCnt() != 0;
209         sendInfoFirst = !sendInfoFirst;
210         return sendInfoFirst ? sendInfoBuffer1 : sendInfoBuffer2;
211     }
212 
213     boolean isSendInfoChanged() {
214         assert sendInfoBuffer.refCnt() != 0;
215         return !QuicheSendInfo.isSameAddress(sendInfoBuffer1, sendInfoBuffer2);
216     }
217 
218     boolean isClosed() {
219         assert connection != -1;
220         return Quiche.quiche_conn_is_closed(connection);
221     }
222 
223     // Let's override finalize() as we want to ensure we never leak memory even if the user will miss to close
224     // Channel that uses this connection and just let it get GC'ed
225     @Override
226     protected void finalize() throws Throwable {
227         try {
228             free(false);
229         } finally {
230             super.finalize();
231         }
232     }
233 }