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          assert connection != -1;
64          this.connection = connection;
65          this.ssl = ssl;
66          this.engine = engine;
67          this.refCnt = refCnt;
68          // TODO: Maybe cache these per thread as we only use them temporary within a limited scope.
69          recvInfoBuffer = Quiche.allocateNativeOrder(TOTAL_RECV_INFO_SIZE);
70          sendInfoBuffer = Quiche.allocateNativeOrder(2 * Quiche.SIZEOF_QUICHE_SEND_INFO);
71  
72          // Let's memset the memory.
73          recvInfoBuffer.setZero(0, recvInfoBuffer.capacity());
74          sendInfoBuffer.setZero(0, sendInfoBuffer.capacity());
75  
76          recvInfoBuffer1 = recvInfoBuffer.nioBuffer(0, TOTAL_RECV_INFO_SIZE);
77          sendInfoBuffer1 = sendInfoBuffer.nioBuffer(0, Quiche.SIZEOF_QUICHE_SEND_INFO);
78          sendInfoBuffer2 = sendInfoBuffer.nioBuffer(Quiche.SIZEOF_QUICHE_SEND_INFO, Quiche.SIZEOF_QUICHE_SEND_INFO);
79          this.engine.connection = this;
80          leakTracker = leakDetector.track(this);
81      }
82  
83      synchronized void reattach(ReferenceCounted refCnt) {
84          this.refCnt.release();
85          this.refCnt = refCnt;
86      }
87  
88      void free() {
89          free(true);
90      }
91  
92      boolean isFreed() {
93          return connection == -1;
94      }
95  
96      private void free(boolean closeLeakTracker) {
97          boolean release = false;
98          synchronized (this) {
99              if (connection != -1) {
100                 try {
101                     BoringSSL.SSL_cleanup(ssl);
102                     Quiche.quiche_conn_free(connection);
103                     engine.ctx.remove(engine);
104                     release = true;
105                     refCnt.release();
106                 } finally {
107                     connection = -1;
108                 }
109             }
110         }
111         if (release) {
112             recvInfoBuffer.release();
113             sendInfoBuffer.release();
114             if (closeLeakTracker && leakTracker != null) {
115                 leakTracker.close(this);
116             }
117         }
118     }
119 
120     @Nullable
121     Runnable sslTask() {
122         final Runnable task;
123         synchronized (this) {
124             if (connection != -1) {
125                 task = BoringSSL.SSL_getTask(ssl);
126             } else {
127                 task = null;
128             }
129         }
130         if (task == null) {
131             return null;
132         }
133 
134         return () -> {
135             if (connection == -1) {
136                 return;
137             }
138 
139             task.run();
140         };
141     }
142 
143     @Nullable
144     QuicConnectionAddress sourceId() {
145         return connectionId(() -> Quiche.quiche_conn_source_id(connection));
146     }
147 
148     @Nullable
149     QuicConnectionAddress destinationId() {
150         return connectionId(() -> Quiche.quiche_conn_destination_id(connection));
151     }
152 
153     @Nullable
154     QuicConnectionAddress connectionId(Supplier<byte[]> idSupplier) {
155         final byte[] id;
156         synchronized (this) {
157             if (connection == -1) {
158                 return null;
159             }
160             id = idSupplier.get();
161         }
162         return id == null ? QuicConnectionAddress.NULL_LEN : new QuicConnectionAddress(id);
163     }
164 
165     @Nullable
166     QuicheQuicTransportParameters peerParameters() {
167         final long[] ret;
168         synchronized (this) {
169             if (connection == -1) {
170                 return null;
171             }
172             ret = Quiche.quiche_conn_peer_transport_params(connection);
173         }
174         if (ret == null) {
175             return null;
176         }
177         return new QuicheQuicTransportParameters(ret);
178     }
179 
180     QuicheQuicSslEngine engine() {
181         return engine;
182     }
183 
184     long address() {
185         assert connection != -1;
186         return connection;
187     }
188 
189     void init(InetSocketAddress local, InetSocketAddress remote, Consumer<String> sniSelectedCallback) {
190         assert connection != -1;
191         assert recvInfoBuffer.refCnt() != 0;
192         assert sendInfoBuffer.refCnt() != 0;
193 
194         // Fill quiche_recv_info struct with the addresses.
195         QuicheRecvInfo.setRecvInfo(recvInfoBuffer1, remote, local);
196 
197         // Fill both quiche_send_info structs with the same addresses.
198         QuicheSendInfo.setSendInfo(sendInfoBuffer1, local, remote);
199         QuicheSendInfo.setSendInfo(sendInfoBuffer2, local, remote);
200         engine.sniSelectedCallback = sniSelectedCallback;
201     }
202 
203     ByteBuffer nextRecvInfo() {
204         assert recvInfoBuffer.refCnt() != 0;
205         return recvInfoBuffer1;
206     }
207 
208     ByteBuffer nextSendInfo() {
209         assert sendInfoBuffer.refCnt() != 0;
210         sendInfoFirst = !sendInfoFirst;
211         return sendInfoFirst ? sendInfoBuffer1 : sendInfoBuffer2;
212     }
213 
214     boolean isSendInfoChanged() {
215         assert sendInfoBuffer.refCnt() != 0;
216         return !QuicheSendInfo.isSameAddress(sendInfoBuffer1, sendInfoBuffer2);
217     }
218 
219     boolean isClosed() {
220         return isFreed() || 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 }