1 /*
2 * Copyright 2014 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.haproxy;
17
18 import io.netty.buffer.ByteBuf;
19 import io.netty.channel.ChannelHandlerContext;
20 import io.netty.handler.codec.ByteToMessageDecoder;
21 import io.netty.handler.codec.ProtocolDetectionResult;
22 import io.netty.util.CharsetUtil;
23
24 import java.util.List;
25
26 import static io.netty.handler.codec.haproxy.HAProxyConstants.*;
27
28 /**
29 * Decodes an HAProxy proxy protocol header
30 *
31 * @see <a href="https://haproxy.1wt.eu/download/1.5/doc/proxy-protocol.txt">Proxy Protocol Specification</a>
32 */
33 public class HAProxyMessageDecoder extends ByteToMessageDecoder {
34 /**
35 * Maximum possible length of a v1 proxy protocol header per spec
36 */
37 private static final int V1_MAX_LENGTH = 108;
38
39 /**
40 * Maximum possible length of a v2 proxy protocol header (fixed 16 bytes + max unsigned short)
41 */
42 private static final int V2_MAX_LENGTH = 16 + 65535;
43
44 /**
45 * Minimum possible length of a fully functioning v2 proxy protocol header (fixed 16 bytes + v2 address info space)
46 */
47 private static final int V2_MIN_LENGTH = 16 + 216;
48
49 /**
50 * Maximum possible length for v2 additional TLV data (max unsigned short - max v2 address info space)
51 */
52 private static final int V2_MAX_TLV = 65535 - 216;
53
54 /**
55 * Binary header prefix length
56 */
57 private static final int BINARY_PREFIX_LENGTH = BINARY_PREFIX.length;
58
59 /**
60 * {@link ProtocolDetectionResult} for {@link HAProxyProtocolVersion#V1}.
61 */
62 private static final ProtocolDetectionResult<HAProxyProtocolVersion> DETECTION_RESULT_V1 =
63 ProtocolDetectionResult.detected(HAProxyProtocolVersion.V1);
64
65 /**
66 * {@link ProtocolDetectionResult} for {@link HAProxyProtocolVersion#V2}.
67 */
68 private static final ProtocolDetectionResult<HAProxyProtocolVersion> DETECTION_RESULT_V2 =
69 ProtocolDetectionResult.detected(HAProxyProtocolVersion.V2);
70
71 /**
72 * Used to extract a header frame out of the {@link ByteBuf} and return it.
73 */
74 private HeaderExtractor headerExtractor;
75
76 /**
77 * {@code true} if we're discarding input because we're already over maxLength
78 */
79 private boolean discarding;
80
81 /**
82 * Number of discarded bytes
83 */
84 private int discardedBytes;
85
86 /**
87 * Whether or not to throw an exception as soon as we exceed maxLength.
88 */
89 private final boolean failFast;
90
91 /**
92 * {@code true} if we're finished decoding the proxy protocol header
93 */
94 private boolean finished;
95
96 /**
97 * Protocol specification version
98 */
99 private int version = -1;
100
101 /**
102 * The latest v2 spec (2014/05/18) allows for additional data to be sent in the proxy protocol header beyond the
103 * address information block so now we need a configurable max header size
104 */
105 private final int v2MaxHeaderSize;
106
107 /**
108 * Creates a new decoder with no additional data (TLV) restrictions, and should throw an exception as soon as
109 * we exceed maxLength.
110 */
111 public HAProxyMessageDecoder() {
112 this(true);
113 }
114
115 /**
116 * Creates a new decoder with no additional data (TLV) restrictions, whether or not to throw an exception as soon
117 * as we exceed maxLength.
118 *
119 * @param failFast Whether or not to throw an exception as soon as we exceed maxLength
120 */
121 public HAProxyMessageDecoder(boolean failFast) {
122 v2MaxHeaderSize = V2_MAX_LENGTH;
123 this.failFast = failFast;
124 }
125
126 /**
127 * Creates a new decoder with restricted additional data (TLV) size, and should throw an exception as soon as
128 * we exceed maxLength.
129 * <p>
130 * <b>Note:</b> limiting TLV size only affects processing of v2, binary headers. Also, as allowed by the 1.5 spec
131 * TLV data is currently ignored. For maximum performance it would be best to configure your upstream proxy host to
132 * <b>NOT</b> send TLV data and instantiate with a max TLV size of {@code 0}.
133 * </p>
134 *
135 * @param maxTlvSize maximum number of bytes allowed for additional data (Type-Length-Value vectors) in a v2 header
136 */
137 public HAProxyMessageDecoder(int maxTlvSize) {
138 this(maxTlvSize, true);
139 }
140
141 /**
142 * Creates a new decoder with restricted additional data (TLV) size, whether or not to throw an exception as soon
143 * as we exceed maxLength.
144 *
145 * @param maxTlvSize maximum number of bytes allowed for additional data (Type-Length-Value vectors) in a v2 header
146 * @param failFast Whether or not to throw an exception as soon as we exceed maxLength
147 */
148 public HAProxyMessageDecoder(int maxTlvSize, boolean failFast) {
149 if (maxTlvSize < 1) {
150 v2MaxHeaderSize = V2_MIN_LENGTH;
151 } else if (maxTlvSize > V2_MAX_TLV) {
152 v2MaxHeaderSize = V2_MAX_LENGTH;
153 } else {
154 int calcMax = maxTlvSize + V2_MIN_LENGTH;
155 if (calcMax > V2_MAX_LENGTH) {
156 v2MaxHeaderSize = V2_MAX_LENGTH;
157 } else {
158 v2MaxHeaderSize = calcMax;
159 }
160 }
161 this.failFast = failFast;
162 }
163
164 /**
165 * Returns the proxy protocol specification version in the buffer if the version is found.
166 * Returns -1 if no version was found in the buffer.
167 */
168 private static int findVersion(final ByteBuf buffer) {
169 final int n = buffer.readableBytes();
170 // per spec, the version number is found in the 13th byte
171 if (n < 13) {
172 return -1;
173 }
174
175 int idx = buffer.readerIndex();
176 return match(BINARY_PREFIX, buffer, idx) ? buffer.getUnsignedByte(idx + BINARY_PREFIX_LENGTH) : 1;
177 }
178
179 /**
180 * Returns the index in the buffer of the end of header if found.
181 * Returns -1 if no end of header was found in the buffer.
182 */
183 private static int findEndOfHeader(final ByteBuf buffer) {
184 final int n = buffer.readableBytes();
185
186 // per spec, the 15th and 16th bytes contain the address length in bytes
187 if (n < 16) {
188 return -1;
189 }
190
191 int offset = buffer.readerIndex() + 14;
192
193 // the total header length will be a fixed 16 byte sequence + the dynamic address information block
194 int totalHeaderBytes = 16 + buffer.getUnsignedShort(offset);
195
196 // ensure we actually have the full header available
197 if (n >= totalHeaderBytes) {
198 return buffer.readerIndex() + totalHeaderBytes;
199 } else {
200 return -1;
201 }
202 }
203
204 /**
205 * Returns the index in the buffer of the end of line found.
206 * Returns -1 if no end of line was found in the buffer.
207 */
208 private static int findEndOfLine(final ByteBuf buffer) {
209 final int n = buffer.writerIndex();
210 for (int i = buffer.readerIndex(); i < n; i++) {
211 final byte b = buffer.getByte(i);
212 if (b == '\r' && i < n - 1 && buffer.getByte(i + 1) == '\n') {
213 return i; // \r\n
214 }
215 }
216 return -1; // Not found.
217 }
218
219 @Override
220 public boolean isSingleDecode() {
221 // ByteToMessageDecoder uses this method to optionally break out of the decoding loop after each unit of work.
222 // Since we only ever want to decode a single header we always return true to save a bit of work here.
223 return true;
224 }
225
226 @Override
227 public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
228 super.channelRead(ctx, msg);
229 if (finished) {
230 ctx.pipeline().remove(this);
231 }
232 }
233
234 @Override
235 protected final void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
236 if (finished) {
237 // The header was already decoded, or decoding failed and the channel is closed. Do not decode the
238 // remaining bytes as another header.
239 return;
240 }
241
242 // determine the specification version
243 if (version == -1) {
244 if ((version = findVersion(in)) == -1) {
245 return;
246 }
247 }
248
249 ByteBuf decoded;
250
251 if (version == 1) {
252 decoded = decodeLine(ctx, in);
253 } else {
254 decoded = decodeStruct(ctx, in);
255 }
256
257 if (decoded != null) {
258 finished = true;
259 try {
260 if (version == 1) {
261 out.add(HAProxyMessage.decodeHeader(decoded.toString(CharsetUtil.US_ASCII)));
262 } else {
263 out.add(HAProxyMessage.decodeHeader(decoded));
264 }
265 } catch (HAProxyProtocolException e) {
266 fail(ctx, null, e);
267 }
268 }
269 }
270
271 /**
272 * Create a frame out of the {@link ByteBuf} and return it.
273 *
274 * @param ctx the {@link ChannelHandlerContext} which this {@link HAProxyMessageDecoder} belongs to
275 * @param buffer the {@link ByteBuf} from which to read data
276 * @return frame the {@link ByteBuf} which represent the frame or {@code null} if no frame could
277 * be created
278 */
279 private ByteBuf decodeStruct(ChannelHandlerContext ctx, ByteBuf buffer) throws Exception {
280 if (headerExtractor == null) {
281 headerExtractor = new StructHeaderExtractor(v2MaxHeaderSize);
282 }
283 return headerExtractor.extract(ctx, buffer);
284 }
285
286 /**
287 * Create a frame out of the {@link ByteBuf} and return it.
288 *
289 * @param ctx the {@link ChannelHandlerContext} which this {@link HAProxyMessageDecoder} belongs to
290 * @param buffer the {@link ByteBuf} from which to read data
291 * @return frame the {@link ByteBuf} which represent the frame or {@code null} if no frame could
292 * be created
293 */
294 private ByteBuf decodeLine(ChannelHandlerContext ctx, ByteBuf buffer) throws Exception {
295 if (headerExtractor == null) {
296 headerExtractor = new LineHeaderExtractor(V1_MAX_LENGTH);
297 }
298 return headerExtractor.extract(ctx, buffer);
299 }
300
301 private void failOverLimit(final ChannelHandlerContext ctx, int length) {
302 failOverLimit(ctx, String.valueOf(length));
303 }
304
305 private void failOverLimit(final ChannelHandlerContext ctx, String length) {
306 int maxLength = version == 1 ? V1_MAX_LENGTH : v2MaxHeaderSize;
307 fail(ctx, "header length (" + length + ") exceeds the allowed maximum (" + maxLength + ')', null);
308 }
309
310 private void fail(final ChannelHandlerContext ctx, String errMsg, Exception e) {
311 finished = true;
312 ctx.close(); // drop connection immediately per spec
313 HAProxyProtocolException ppex;
314 if (errMsg != null && e != null) {
315 ppex = new HAProxyProtocolException(errMsg, e);
316 } else if (errMsg != null) {
317 ppex = new HAProxyProtocolException(errMsg);
318 } else if (e != null) {
319 ppex = new HAProxyProtocolException(e);
320 } else {
321 ppex = new HAProxyProtocolException();
322 }
323 throw ppex;
324 }
325
326 /**
327 * Returns the {@link ProtocolDetectionResult} for the given {@link ByteBuf}.
328 */
329 public static ProtocolDetectionResult<HAProxyProtocolVersion> detectProtocol(ByteBuf buffer) {
330 if (buffer.readableBytes() < 12) {
331 return ProtocolDetectionResult.needsMoreData();
332 }
333
334 int idx = buffer.readerIndex();
335
336 if (match(BINARY_PREFIX, buffer, idx)) {
337 return DETECTION_RESULT_V2;
338 }
339 if (match(TEXT_PREFIX, buffer, idx)) {
340 return DETECTION_RESULT_V1;
341 }
342 return ProtocolDetectionResult.invalid();
343 }
344
345 private static boolean match(byte[] prefix, ByteBuf buffer, int idx) {
346 for (int i = 0; i < prefix.length; i++) {
347 final byte b = buffer.getByte(idx + i);
348 if (b != prefix[i]) {
349 return false;
350 }
351 }
352 return true;
353 }
354
355 /**
356 * HeaderExtractor create a header frame out of the {@link ByteBuf}.
357 */
358 private abstract class HeaderExtractor {
359 /** Header max size */
360 private final int maxHeaderSize;
361
362 protected HeaderExtractor(int maxHeaderSize) {
363 this.maxHeaderSize = maxHeaderSize;
364 }
365
366 /**
367 * Create a frame out of the {@link ByteBuf} and return it.
368 *
369 * @param ctx the {@link ChannelHandlerContext} which this {@link HAProxyMessageDecoder} belongs to
370 * @param buffer the {@link ByteBuf} from which to read data
371 * @return frame the {@link ByteBuf} which represent the frame or {@code null} if no frame could
372 * be created
373 * @throws Exception if exceed maxLength
374 */
375 public ByteBuf extract(ChannelHandlerContext ctx, ByteBuf buffer) throws Exception {
376 final int eoh = findEndOfHeader(buffer);
377 if (!discarding) {
378 if (eoh >= 0) {
379 final int length = eoh - buffer.readerIndex();
380 if (length > maxHeaderSize) {
381 buffer.readerIndex(eoh + delimiterLength(buffer, eoh));
382 failOverLimit(ctx, length);
383 return null;
384 }
385 ByteBuf frame = buffer.readSlice(length);
386 buffer.skipBytes(delimiterLength(buffer, eoh));
387 return frame;
388 } else {
389 final int length = buffer.readableBytes();
390 if (length > maxHeaderSize) {
391 discardedBytes = length;
392 buffer.skipBytes(length);
393 discarding = true;
394 if (failFast) {
395 failOverLimit(ctx, "over " + discardedBytes);
396 }
397 }
398 return null;
399 }
400 } else {
401 if (eoh >= 0) {
402 final int length = discardedBytes + eoh - buffer.readerIndex();
403 buffer.readerIndex(eoh + delimiterLength(buffer, eoh));
404 discardedBytes = 0;
405 discarding = false;
406 if (!failFast) {
407 failOverLimit(ctx, "over " + length);
408 }
409 } else {
410 discardedBytes += buffer.readableBytes();
411 buffer.skipBytes(buffer.readableBytes());
412 }
413 return null;
414 }
415 }
416
417 /**
418 * Find the end of the header from the given {@link ByteBuf},the end may be a CRLF, or the length given by the
419 * header.
420 *
421 * @param buffer the buffer to be searched
422 * @return {@code -1} if can not find the end, otherwise return the buffer index of end
423 */
424 protected abstract int findEndOfHeader(ByteBuf buffer);
425
426 /**
427 * Get the length of the header delimiter.
428 *
429 * @param buffer the buffer where delimiter is located
430 * @param eoh index of delimiter
431 * @return length of the delimiter
432 */
433 protected abstract int delimiterLength(ByteBuf buffer, int eoh);
434 }
435
436 private final class LineHeaderExtractor extends HeaderExtractor {
437
438 LineHeaderExtractor(int maxHeaderSize) {
439 super(maxHeaderSize);
440 }
441
442 @Override
443 protected int findEndOfHeader(ByteBuf buffer) {
444 return findEndOfLine(buffer);
445 }
446
447 @Override
448 protected int delimiterLength(ByteBuf buffer, int eoh) {
449 return buffer.getByte(eoh) == '\r' ? 2 : 1;
450 }
451 }
452
453 private final class StructHeaderExtractor extends HeaderExtractor {
454
455 StructHeaderExtractor(int maxHeaderSize) {
456 super(maxHeaderSize);
457 }
458
459 @Override
460 protected int findEndOfHeader(ByteBuf buffer) {
461 return HAProxyMessageDecoder.findEndOfHeader(buffer);
462 }
463
464 @Override
465 protected int delimiterLength(ByteBuf buffer, int eoh) {
466 return 0;
467 }
468 }
469 }