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 volatile long connection;
61  
62      // Number of SSL tasks that are currently running (possibly on a different Thread than the EventLoop).
63      // Guarded by this.
64      private int runningSslTasks;
65  
66      // Set if free() was called while SSL tasks were still running. The native memory is then released once the last
67      // running task completed. Guarded by this for writes.
68      private volatile boolean freePending;
69  
70      QuicheQuicConnection(long connection, long ssl, QuicheQuicSslEngine engine, ReferenceCounted refCnt) {
71          assert connection != -1;
72          this.connection = connection;
73          this.ssl = ssl;
74          this.engine = engine;
75          this.refCnt = refCnt;
76          // TODO: Maybe cache these per thread as we only use them temporary within a limited scope.
77          recvInfoBuffer = Quiche.allocateNativeOrder(TOTAL_RECV_INFO_SIZE);
78          sendInfoBuffer = Quiche.allocateNativeOrder(2 * Quiche.SIZEOF_QUICHE_SEND_INFO);
79  
80          // Let's memset the memory.
81          recvInfoBuffer.setZero(0, recvInfoBuffer.capacity());
82          sendInfoBuffer.setZero(0, sendInfoBuffer.capacity());
83  
84          recvInfoBuffer1 = recvInfoBuffer.nioBuffer(0, TOTAL_RECV_INFO_SIZE);
85          sendInfoBuffer1 = sendInfoBuffer.nioBuffer(0, Quiche.SIZEOF_QUICHE_SEND_INFO);
86          sendInfoBuffer2 = sendInfoBuffer.nioBuffer(Quiche.SIZEOF_QUICHE_SEND_INFO, Quiche.SIZEOF_QUICHE_SEND_INFO);
87          this.engine.connection = this;
88          leakTracker = leakDetector.track(this);
89      }
90  
91      synchronized void reattach(ReferenceCounted refCnt) {
92          this.refCnt.release();
93          this.refCnt = refCnt;
94      }
95  
96      void free() {
97          free(true);
98      }
99  
100     boolean isFreed() {
101         // If a free is pending the connection must not be used anymore by the caller.
102         return connection == -1 || freePending;
103     }
104 
105     private void free(boolean closeLeakTracker) {
106         boolean release = false;
107         synchronized (this) {
108             if (connection != -1) {
109                 if (runningSslTasks > 0) {
110                     // An SSL task is still using the native SSL object, we must not release it now. The last running
111                     // task will do so once it completed.
112                     freePending = true;
113                     return;
114                 }
115                 try {
116                     BoringSSL.SSL_cleanup(ssl);
117                     Quiche.quiche_conn_free(connection);
118                     engine.ctx.remove(engine);
119                     release = true;
120                     refCnt.release();
121                 } finally {
122                     connection = -1;
123                 }
124             }
125         }
126         if (release) {
127             recvInfoBuffer.release();
128             sendInfoBuffer.release();
129             if (closeLeakTracker && leakTracker != null) {
130                 leakTracker.close(this);
131             }
132         }
133     }
134 
135     @Nullable
136     Runnable sslTask() {
137         final Runnable task;
138         synchronized (this) {
139             if (connection != -1 && !freePending) {
140                 task = BoringSSL.SSL_getTask(ssl);
141             } else {
142                 task = null;
143             }
144         }
145         if (task == null) {
146             return null;
147         }
148 
149         return () -> {
150             synchronized (this) {
151                 if (connection == -1 || freePending) {
152                     return;
153                 }
154                 // Prevent free() from releasing the native memory while the task is running. We can't hold the
155                 // monitor while running the task as it might block for a long time and free() is called from the
156                 // EventLoop.
157                 runningSslTasks++;
158             }
159             try {
160                 task.run();
161             } finally {
162                 boolean free;
163                 synchronized (this) {
164                     free = --runningSslTasks == 0 && freePending;
165                 }
166                 if (free) {
167                     free(true);
168                 }
169             }
170         };
171     }
172 
173     @Nullable
174     QuicConnectionAddress sourceId() {
175         return connectionId(() -> Quiche.quiche_conn_source_id(connection));
176     }
177 
178     @Nullable
179     QuicConnectionAddress destinationId() {
180         return connectionId(() -> Quiche.quiche_conn_destination_id(connection));
181     }
182 
183     @Nullable
184     QuicConnectionAddress connectionId(Supplier<byte[]> idSupplier) {
185         final byte[] id;
186         synchronized (this) {
187             if (connection == -1) {
188                 return null;
189             }
190             id = idSupplier.get();
191         }
192         return id == null ? QuicConnectionAddress.NULL_LEN : new QuicConnectionAddress(id);
193     }
194 
195     @Nullable
196     QuicheQuicTransportParameters peerParameters() {
197         final long[] ret;
198         synchronized (this) {
199             if (connection == -1) {
200                 return null;
201             }
202             ret = Quiche.quiche_conn_peer_transport_params(connection);
203         }
204         if (ret == null) {
205             return null;
206         }
207         return new QuicheQuicTransportParameters(ret);
208     }
209 
210     QuicheQuicSslEngine engine() {
211         return engine;
212     }
213 
214     long address() {
215         assert connection != -1;
216         return connection;
217     }
218 
219     void init(InetSocketAddress local, InetSocketAddress remote, Consumer<String> sniSelectedCallback) {
220         assert connection != -1;
221         assert recvInfoBuffer.refCnt() != 0;
222         assert sendInfoBuffer.refCnt() != 0;
223 
224         // Fill quiche_recv_info struct with the addresses.
225         QuicheRecvInfo.setRecvInfo(recvInfoBuffer1, remote, local);
226 
227         // Fill both quiche_send_info structs with the same addresses.
228         QuicheSendInfo.setSendInfo(sendInfoBuffer1, local, remote);
229         QuicheSendInfo.setSendInfo(sendInfoBuffer2, local, remote);
230         engine.sniSelectedCallback = sniSelectedCallback;
231     }
232 
233     ByteBuffer nextRecvInfo() {
234         assert recvInfoBuffer.refCnt() != 0;
235         return recvInfoBuffer1;
236     }
237 
238     ByteBuffer nextSendInfo() {
239         assert sendInfoBuffer.refCnt() != 0;
240         sendInfoFirst = !sendInfoFirst;
241         return sendInfoFirst ? sendInfoBuffer1 : sendInfoBuffer2;
242     }
243 
244     boolean isSendInfoChanged() {
245         assert sendInfoBuffer.refCnt() != 0;
246         return !QuicheSendInfo.isSameAddress(sendInfoBuffer1, sendInfoBuffer2);
247     }
248 
249     boolean isClosed() {
250         return isFreed() || Quiche.quiche_conn_is_closed(connection);
251     }
252 
253     // Let's override finalize() as we want to ensure we never leak memory even if the user will miss to close
254     // Channel that uses this connection and just let it get GC'ed
255     @Override
256     protected void finalize() throws Throwable {
257         try {
258             free(false);
259         } finally {
260             super.finalize();
261         }
262     }
263 }