1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16 package io.netty.handler.ssl;
17
18 import io.netty.buffer.ByteBuf;
19 import io.netty.channel.ChannelHandlerContext;
20 import io.netty.util.CharsetUtil;
21 import io.netty.util.concurrent.Future;
22 import io.netty.util.concurrent.ScheduledFuture;
23
24 import java.util.Locale;
25 import java.util.concurrent.TimeUnit;
26
27 import static io.netty.util.internal.ObjectUtil.checkPositiveOrZero;
28
29
30
31
32
33
34
35
36 public abstract class AbstractSniHandler<T> extends SslClientHelloHandler<T> {
37
38
39 static String extractSniHostname(ByteBuf in) {
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60 int offset = in.readerIndex();
61 int endOffset = in.writerIndex();
62 offset += 34;
63
64 if (endOffset - offset >= 6) {
65 final int sessionIdLength = in.getUnsignedByte(offset);
66 offset += sessionIdLength + 1;
67
68 if (endOffset - offset >= 2) {
69 final int cipherSuitesLength = in.getUnsignedShort(offset);
70 offset += cipherSuitesLength + 2;
71
72 if (endOffset - offset >= 1) {
73 final int compressionMethodLength = in.getUnsignedByte(offset);
74 offset += compressionMethodLength + 1;
75
76 if (endOffset - offset >= 2) {
77 final int extensionsLength = in.getUnsignedShort(offset);
78 offset += 2;
79 final int extensionsLimit = offset + extensionsLength;
80
81
82 if (extensionsLimit <= endOffset) {
83 while (extensionsLimit - offset >= 4) {
84 final int extensionType = in.getUnsignedShort(offset);
85 offset += 2;
86
87 final int extensionLength = in.getUnsignedShort(offset);
88 offset += 2;
89
90 if (extensionsLimit - offset < extensionLength) {
91 break;
92 }
93
94
95
96 if (extensionType == 0) {
97 offset += 2;
98 if (extensionsLimit - offset < 3) {
99 break;
100 }
101
102 final int serverNameType = in.getUnsignedByte(offset);
103 offset++;
104
105 if (serverNameType == 0) {
106 final int serverNameLength = in.getUnsignedShort(offset);
107 offset += 2;
108
109 if (extensionsLimit - offset < serverNameLength) {
110 break;
111 }
112
113 final String hostname = in.toString(
114 offset, serverNameLength, CharsetUtil.US_ASCII);
115 return hostname.toLowerCase(Locale.US);
116 } else {
117
118 break;
119 }
120 }
121
122 offset += extensionLength;
123 }
124 }
125 }
126 }
127 }
128 }
129 return null;
130 }
131
132 static final long DEFAULT_HANDSHAKE_TIMEOUT_MILLIS = TimeUnit.SECONDS.toMillis(10);
133 protected final long handshakeTimeoutMillis;
134 private ScheduledFuture<?> timeoutFuture;
135 private String hostname;
136
137
138
139
140 protected AbstractSniHandler(long handshakeTimeoutMillis) {
141 this(DEFAULT_MAX_CLIENT_HELLO_LENGTH, handshakeTimeoutMillis);
142 }
143
144
145
146
147
148 protected AbstractSniHandler(int maxClientHelloLength, long handshakeTimeoutMillis) {
149 super(maxClientHelloLength);
150 this.handshakeTimeoutMillis = checkPositiveOrZero(handshakeTimeoutMillis, "handshakeTimeoutMillis");
151 }
152
153 public AbstractSniHandler() {
154 this(DEFAULT_MAX_CLIENT_HELLO_LENGTH, DEFAULT_HANDSHAKE_TIMEOUT_MILLIS);
155 }
156
157 @Override
158 public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
159 if (ctx.channel().isActive()) {
160 checkStartTimeout(ctx);
161 }
162 }
163
164 @Override
165 public void channelActive(ChannelHandlerContext ctx) throws Exception {
166 ctx.fireChannelActive();
167 checkStartTimeout(ctx);
168 }
169
170 private void checkStartTimeout(final ChannelHandlerContext ctx) {
171 if (handshakeTimeoutMillis <= 0 || timeoutFuture != null) {
172 return;
173 }
174 timeoutFuture = ctx.executor().schedule(new Runnable() {
175 @Override
176 public void run() {
177 if (ctx.channel().isActive()) {
178 SslHandshakeTimeoutException exception = new SslHandshakeTimeoutException(
179 "handshake timed out after " + handshakeTimeoutMillis + "ms");
180 ctx.fireUserEventTriggered(new SniCompletionEvent(exception));
181 ctx.close();
182 }
183 }
184 }, handshakeTimeoutMillis, TimeUnit.MILLISECONDS);
185 }
186
187 @Override
188 protected Future<T> lookup(ChannelHandlerContext ctx, ByteBuf clientHello) throws Exception {
189 hostname = clientHello == null ? null : extractSniHostname(clientHello);
190
191 return lookup(ctx, hostname);
192 }
193
194 @Override
195 protected void onLookupComplete(ChannelHandlerContext ctx, Future<T> future) throws Exception {
196 if (timeoutFuture != null) {
197 timeoutFuture.cancel(false);
198 }
199 try {
200 onLookupComplete(ctx, hostname, future);
201 } finally {
202 fireSniCompletionEvent(ctx, hostname, future);
203 }
204 }
205
206
207
208
209
210
211
212 protected abstract Future<T> lookup(ChannelHandlerContext ctx, String hostname) throws Exception;
213
214
215
216
217
218
219 protected abstract void onLookupComplete(ChannelHandlerContext ctx,
220 String hostname, Future<T> future) throws Exception;
221
222 private static void fireSniCompletionEvent(ChannelHandlerContext ctx, String hostname, Future<?> future) {
223 Throwable cause = future.cause();
224 if (cause == null) {
225 ctx.fireUserEventTriggered(new SniCompletionEvent(hostname));
226 } else {
227 ctx.fireUserEventTriggered(new SniCompletionEvent(hostname, cause));
228 }
229 }
230 }