View Javadoc
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) {  // lgtm[java/constant-comparison]
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.getByte(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 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         // determine the specification version
237         if (version == -1) {
238             if ((version = findVersion(in)) == -1) {
239                 return;
240             }
241         }
242 
243         ByteBuf decoded;
244 
245         if (version == 1) {
246             decoded = decodeLine(ctx, in);
247         } else {
248             decoded = decodeStruct(ctx, in);
249         }
250 
251         if (decoded != null) {
252             finished = true;
253             try {
254                 if (version == 1) {
255                     out.add(HAProxyMessage.decodeHeader(decoded.toString(CharsetUtil.US_ASCII)));
256                 } else {
257                     out.add(HAProxyMessage.decodeHeader(decoded));
258                 }
259             } catch (HAProxyProtocolException e) {
260                 fail(ctx, null, e);
261             }
262         }
263     }
264 
265     /**
266      * Create a frame out of the {@link ByteBuf} and return it.
267      *
268      * @param ctx     the {@link ChannelHandlerContext} which this {@link HAProxyMessageDecoder} belongs to
269      * @param buffer  the {@link ByteBuf} from which to read data
270      * @return frame  the {@link ByteBuf} which represent the frame or {@code null} if no frame could
271      *                be created
272      */
273     private ByteBuf decodeStruct(ChannelHandlerContext ctx, ByteBuf buffer) throws Exception {
274         if (headerExtractor == null) {
275             headerExtractor = new StructHeaderExtractor(v2MaxHeaderSize);
276         }
277         return headerExtractor.extract(ctx, buffer);
278     }
279 
280     /**
281      * Create a frame out of the {@link ByteBuf} and return it.
282      *
283      * @param ctx     the {@link ChannelHandlerContext} which this {@link HAProxyMessageDecoder} belongs to
284      * @param buffer  the {@link ByteBuf} from which to read data
285      * @return frame  the {@link ByteBuf} which represent the frame or {@code null} if no frame could
286      *                be created
287      */
288     private ByteBuf decodeLine(ChannelHandlerContext ctx, ByteBuf buffer) throws Exception {
289         if (headerExtractor == null) {
290             headerExtractor = new LineHeaderExtractor(V1_MAX_LENGTH);
291         }
292         return headerExtractor.extract(ctx, buffer);
293     }
294 
295     private void failOverLimit(final ChannelHandlerContext ctx, int length) {
296         failOverLimit(ctx, String.valueOf(length));
297     }
298 
299     private void failOverLimit(final ChannelHandlerContext ctx, String length) {
300         int maxLength = version == 1 ? V1_MAX_LENGTH : v2MaxHeaderSize;
301         fail(ctx, "header length (" + length + ") exceeds the allowed maximum (" + maxLength + ')', null);
302     }
303 
304     private void fail(final ChannelHandlerContext ctx, String errMsg, Exception e) {
305         finished = true;
306         ctx.close(); // drop connection immediately per spec
307         HAProxyProtocolException ppex;
308         if (errMsg != null && e != null) {
309             ppex = new HAProxyProtocolException(errMsg, e);
310         } else if (errMsg != null) {
311             ppex = new HAProxyProtocolException(errMsg);
312         } else if (e != null) {
313             ppex = new HAProxyProtocolException(e);
314         } else {
315             ppex = new HAProxyProtocolException();
316         }
317         throw ppex;
318     }
319 
320     /**
321      * Returns the {@link ProtocolDetectionResult} for the given {@link ByteBuf}.
322      */
323     public static ProtocolDetectionResult<HAProxyProtocolVersion> detectProtocol(ByteBuf buffer) {
324         if (buffer.readableBytes() < 12) {
325             return ProtocolDetectionResult.needsMoreData();
326         }
327 
328         int idx = buffer.readerIndex();
329 
330         if (match(BINARY_PREFIX, buffer, idx)) {
331             return DETECTION_RESULT_V2;
332         }
333         if (match(TEXT_PREFIX, buffer, idx)) {
334             return DETECTION_RESULT_V1;
335         }
336         return ProtocolDetectionResult.invalid();
337     }
338 
339     private static boolean match(byte[] prefix, ByteBuf buffer, int idx) {
340         for (int i = 0; i < prefix.length; i++) {
341             final byte b = buffer.getByte(idx + i);
342             if (b != prefix[i]) {
343                 return false;
344             }
345         }
346         return true;
347     }
348 
349     /**
350      * HeaderExtractor create a header frame out of the {@link ByteBuf}.
351      */
352     private abstract class HeaderExtractor {
353         /** Header max size */
354         private final int maxHeaderSize;
355 
356         protected HeaderExtractor(int maxHeaderSize) {
357             this.maxHeaderSize = maxHeaderSize;
358         }
359 
360         /**
361          * Create a frame out of the {@link ByteBuf} and return it.
362          *
363          * @param ctx     the {@link ChannelHandlerContext} which this {@link HAProxyMessageDecoder} belongs to
364          * @param buffer  the {@link ByteBuf} from which to read data
365          * @return frame  the {@link ByteBuf} which represent the frame or {@code null} if no frame could
366          *                be created
367          * @throws Exception if exceed maxLength
368          */
369         public ByteBuf extract(ChannelHandlerContext ctx, ByteBuf buffer) throws Exception {
370             final int eoh = findEndOfHeader(buffer);
371             if (!discarding) {
372                 if (eoh >= 0) {
373                     final int length = eoh - buffer.readerIndex();
374                     if (length > maxHeaderSize) {
375                         buffer.readerIndex(eoh + delimiterLength(buffer, eoh));
376                         failOverLimit(ctx, length);
377                         return null;
378                     }
379                     ByteBuf frame = buffer.readSlice(length);
380                     buffer.skipBytes(delimiterLength(buffer, eoh));
381                     return frame;
382                 } else {
383                     final int length = buffer.readableBytes();
384                     if (length > maxHeaderSize) {
385                         discardedBytes = length;
386                         buffer.skipBytes(length);
387                         discarding = true;
388                         if (failFast) {
389                             failOverLimit(ctx, "over " + discardedBytes);
390                         }
391                     }
392                     return null;
393                 }
394             } else {
395                 if (eoh >= 0) {
396                     final int length = discardedBytes + eoh - buffer.readerIndex();
397                     buffer.readerIndex(eoh + delimiterLength(buffer, eoh));
398                     discardedBytes = 0;
399                     discarding = false;
400                     if (!failFast) {
401                         failOverLimit(ctx, "over " + length);
402                     }
403                 } else {
404                     discardedBytes += buffer.readableBytes();
405                     buffer.skipBytes(buffer.readableBytes());
406                 }
407                 return null;
408             }
409         }
410 
411         /**
412          * Find the end of the header from the given {@link ByteBuf},the end may be a CRLF, or the length given by the
413          * header.
414          *
415          * @param buffer the buffer to be searched
416          * @return {@code -1} if can not find the end, otherwise return the buffer index of end
417          */
418         protected abstract int findEndOfHeader(ByteBuf buffer);
419 
420         /**
421          * Get the length of the header delimiter.
422          *
423          * @param buffer the buffer where delimiter is located
424          * @param eoh index of delimiter
425          * @return length of the delimiter
426          */
427         protected abstract int delimiterLength(ByteBuf buffer, int eoh);
428     }
429 
430     private final class LineHeaderExtractor extends HeaderExtractor {
431 
432         LineHeaderExtractor(int maxHeaderSize) {
433             super(maxHeaderSize);
434         }
435 
436         @Override
437         protected int findEndOfHeader(ByteBuf buffer) {
438             return findEndOfLine(buffer);
439         }
440 
441         @Override
442         protected int delimiterLength(ByteBuf buffer, int eoh) {
443             return buffer.getByte(eoh) == '\r' ? 2 : 1;
444         }
445     }
446 
447     private final class StructHeaderExtractor extends HeaderExtractor {
448 
449         StructHeaderExtractor(int maxHeaderSize) {
450             super(maxHeaderSize);
451         }
452 
453         @Override
454         protected int findEndOfHeader(ByteBuf buffer) {
455             return HAProxyMessageDecoder.findEndOfHeader(buffer);
456         }
457 
458         @Override
459         protected int delimiterLength(ByteBuf buffer, int eoh) {
460             return 0;
461         }
462     }
463 }