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    *   http://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.LineBasedFrameDecoder;
22  import io.netty.util.CharsetUtil;
23  
24  import java.util.List;
25  
26  /**
27   * Decodes an HAProxy proxy protocol header
28   *
29   * @see <a href="http://haproxy.1wt.eu/download/1.5/doc/proxy-protocol.txt">Proxy Protocol Specification</a>
30   */
31  public class HAProxyMessageDecoder extends ByteToMessageDecoder {
32      /**
33       * Maximum possible length of a v1 proxy protocol header per spec
34       */
35      private static final int V1_MAX_LENGTH = 108;
36  
37      /**
38       * Maximum possible length of a v2 proxy protocol header (fixed 16 bytes + max unsigned short)
39       */
40      private static final int V2_MAX_LENGTH = 16 + 65535;
41  
42      /**
43       * Minimum possible length of a fully functioning v2 proxy protocol header (fixed 16 bytes + v2 address info space)
44       */
45      private static final int V2_MIN_LENGTH = 16 + 216;
46  
47      /**
48       * Maximum possible length for v2 additional TLV data (max unsigned short - max v2 address info space)
49       */
50      private static final int V2_MAX_TLV = 65535 - 216;
51  
52      /**
53       * Version 1 header delimiter is always '\r\n' per spec
54       */
55      private static final int DELIMITER_LENGTH = 2;
56  
57      /**
58       * Binary header prefix
59       */
60      private static final byte[] BINARY_PREFIX = {
61              (byte) 0x0D,
62              (byte) 0x0A,
63              (byte) 0x0D,
64              (byte) 0x0A,
65              (byte) 0x00,
66              (byte) 0x0D,
67              (byte) 0x0A,
68              (byte) 0x51,
69              (byte) 0x55,
70              (byte) 0x49,
71              (byte) 0x54,
72              (byte) 0x0A
73      };
74  
75      /**
76       * Binary header prefix length
77       */
78      private static final int BINARY_PREFIX_LENGTH = BINARY_PREFIX.length;
79  
80      /**
81       * {@code true} if we're discarding input because we're already over maxLength
82       */
83      private boolean discarding;
84  
85      /**
86       * Number of discarded bytes
87       */
88      private int discardedBytes;
89  
90      /**
91       * {@code true} if we're finished decoding the proxy protocol header
92       */
93      private boolean finished;
94  
95      /**
96       * Protocol specification version
97       */
98      private int version = -1;
99  
100     /**
101      * The latest v2 spec (2014/05/18) allows for additional data to be sent in the proxy protocol header beyond the
102      * address information block so now we need a configurable max header size
103      */
104     private final int v2MaxHeaderSize;
105 
106     /**
107      * Creates a new decoder with no additional data (TLV) restrictions
108      */
109     public HAProxyMessageDecoder() {
110         v2MaxHeaderSize = V2_MAX_LENGTH;
111     }
112 
113     /**
114      * Creates a new decoder with restricted additional data (TLV) size
115      * <p>
116      * <b>Note:</b> limiting TLV size only affects processing of v2, binary headers. Also, as allowed by the 1.5 spec
117      * TLV data is currently ignored. For maximum performance it would be best to configure your upstream proxy host to
118      * <b>NOT</b> send TLV data and instantiate with a max TLV size of {@code 0}.
119      * </p>
120      *
121      * @param maxTlvSize maximum number of bytes allowed for additional data (Type-Length-Value vectors) in a v2 header
122      */
123     public HAProxyMessageDecoder(int maxTlvSize) {
124         if (maxTlvSize < 1) {
125             v2MaxHeaderSize = V2_MIN_LENGTH;
126         } else if (maxTlvSize > V2_MAX_TLV) {
127             v2MaxHeaderSize = V2_MAX_LENGTH;
128         } else {
129             int calcMax = maxTlvSize + V2_MIN_LENGTH;
130             if (calcMax > V2_MAX_LENGTH) {
131                 v2MaxHeaderSize = V2_MAX_LENGTH;
132             } else {
133                 v2MaxHeaderSize = calcMax;
134             }
135         }
136     }
137 
138     /**
139      * Returns the proxy protocol specification version in the buffer if the version is found.
140      * Returns -1 if no version was found in the buffer.
141      */
142     private static int findVersion(final ByteBuf buffer) {
143         final int n = buffer.readableBytes();
144         // per spec, the version number is found in the 13th byte
145         if (n < 13) {
146             return -1;
147         }
148 
149         int idx = buffer.readerIndex();
150 
151         for (int i = 0; i < BINARY_PREFIX_LENGTH; i++) {
152             final byte b = buffer.getByte(idx + i);
153             if (b != BINARY_PREFIX[i]) {
154                 return 1;
155             }
156         }
157 
158         return buffer.getByte(idx + BINARY_PREFIX_LENGTH);
159     }
160 
161     /**
162      * Returns the index in the buffer of the end of header if found.
163      * Returns -1 if no end of header was found in the buffer.
164      */
165     private static int findEndOfHeader(final ByteBuf buffer) {
166         final int n = buffer.readableBytes();
167 
168         // per spec, the 15th and 16th bytes contain the address length in bytes
169         if (n < 16) {
170             return -1;
171         }
172 
173         int offset = buffer.readerIndex() + 14;
174 
175         // the total header length will be a fixed 16 byte sequence + the dynamic address information block
176         int totalHeaderBytes = 16 + buffer.getUnsignedShort(offset);
177 
178         // ensure we actually have the full header available
179         if (n >= totalHeaderBytes) {
180             return totalHeaderBytes;
181         } else {
182             return -1;
183         }
184     }
185 
186     /**
187      * Returns the index in the buffer of the end of line found.
188      * Returns -1 if no end of line was found in the buffer.
189      */
190     private static int findEndOfLine(final ByteBuf buffer) {
191         final int n = buffer.writerIndex();
192         for (int i = buffer.readerIndex(); i < n; i++) {
193             final byte b = buffer.getByte(i);
194             if (b == '\r' && i < n - 1 && buffer.getByte(i + 1) == '\n') {
195                 return i;  // \r\n
196             }
197         }
198         return -1;  // Not found.
199     }
200 
201     @Override
202     public boolean isSingleDecode() {
203         // ByteToMessageDecoder uses this method to optionally break out of the decoding loop after each unit of work.
204         // Since we only ever want to decode a single header we always return true to save a bit of work here.
205         return true;
206     }
207 
208     @Override
209     public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
210         super.channelRead(ctx, msg);
211         if (finished) {
212             ctx.pipeline().remove(this);
213         }
214     }
215 
216     @Override
217     protected final void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
218         // determine the specification version
219         if (version == -1) {
220             if ((version = findVersion(in)) == -1) {
221                 return;
222             }
223         }
224 
225         ByteBuf decoded;
226 
227         if (version == 1) {
228             decoded = decodeLine(ctx, in);
229         } else {
230             decoded = decodeStruct(ctx, in);
231         }
232 
233         if (decoded != null) {
234             finished = true;
235             try {
236                 if (version == 1) {
237                     out.add(HAProxyMessage.decodeHeader(decoded.toString(CharsetUtil.US_ASCII)));
238                 } else {
239                     out.add(HAProxyMessage.decodeHeader(decoded));
240                 }
241             } catch (HAProxyProtocolException e) {
242                 fail(ctx, null, e);
243             }
244         }
245     }
246 
247     /**
248      * Create a frame out of the {@link ByteBuf} and return it.
249      * Based on code from {@link LineBasedFrameDecoder#decode(ChannelHandlerContext, ByteBuf)}.
250      *
251      * @param ctx     the {@link ChannelHandlerContext} which this {@link HAProxyMessageDecoder} belongs to
252      * @param buffer  the {@link ByteBuf} from which to read data
253      * @return frame  the {@link ByteBuf} which represent the frame or {@code null} if no frame could
254      *                be created
255      */
256     private ByteBuf decodeStruct(ChannelHandlerContext ctx, ByteBuf buffer) throws Exception {
257         final int eoh = findEndOfHeader(buffer);
258         if (!discarding) {
259             if (eoh >= 0) {
260                 final int length = eoh - buffer.readerIndex();
261                 if (length > v2MaxHeaderSize) {
262                     buffer.readerIndex(eoh);
263                     failOverLimit(ctx, length);
264                     return null;
265                 }
266                 return buffer.readSlice(length);
267             } else {
268                 final int length = buffer.readableBytes();
269                 if (length > v2MaxHeaderSize) {
270                     discardedBytes = length;
271                     buffer.skipBytes(length);
272                     discarding = true;
273                     failOverLimit(ctx, "over " + discardedBytes);
274                 }
275                 return null;
276             }
277         } else {
278             if (eoh >= 0) {
279                 buffer.readerIndex(eoh);
280                 discardedBytes = 0;
281                 discarding = false;
282             } else {
283                 discardedBytes = buffer.readableBytes();
284                 buffer.skipBytes(discardedBytes);
285             }
286             return null;
287         }
288     }
289 
290     /**
291      * Create a frame out of the {@link ByteBuf} and return it.
292      * Based on code from {@link LineBasedFrameDecoder#decode(ChannelHandlerContext, ByteBuf)}.
293      *
294      * @param ctx     the {@link ChannelHandlerContext} which this {@link HAProxyMessageDecoder} belongs to
295      * @param buffer  the {@link ByteBuf} from which to read data
296      * @return frame  the {@link ByteBuf} which represent the frame or {@code null} if no frame could
297      *                be created
298      */
299     private ByteBuf decodeLine(ChannelHandlerContext ctx, ByteBuf buffer) throws Exception {
300         final int eol = findEndOfLine(buffer);
301         if (!discarding) {
302             if (eol >= 0) {
303                 final int length = eol - buffer.readerIndex();
304                 if (length > V1_MAX_LENGTH) {
305                     buffer.readerIndex(eol + DELIMITER_LENGTH);
306                     failOverLimit(ctx, length);
307                     return null;
308                 }
309                 ByteBuf frame = buffer.readSlice(length);
310                 buffer.skipBytes(DELIMITER_LENGTH);
311                 return frame;
312             } else {
313                 final int length = buffer.readableBytes();
314                 if (length > V1_MAX_LENGTH) {
315                     discardedBytes = length;
316                     buffer.skipBytes(length);
317                     discarding = true;
318                     failOverLimit(ctx, "over " + discardedBytes);
319                 }
320                 return null;
321             }
322         } else {
323             if (eol >= 0) {
324                 final int delimLength = buffer.getByte(eol) == '\r' ? 2 : 1;
325                 buffer.readerIndex(eol + delimLength);
326                 discardedBytes = 0;
327                 discarding = false;
328             } else {
329                 discardedBytes = buffer.readableBytes();
330                 buffer.skipBytes(discardedBytes);
331             }
332             return null;
333         }
334     }
335 
336     private void failOverLimit(final ChannelHandlerContext ctx, int length) {
337         failOverLimit(ctx, String.valueOf(length));
338     }
339 
340     private void failOverLimit(final ChannelHandlerContext ctx, String length) {
341         int maxLength = version == 1 ? V1_MAX_LENGTH : v2MaxHeaderSize;
342         fail(ctx, "header length (" + length + ") exceeds the allowed maximum (" + maxLength + ')', null);
343     }
344 
345     private void fail(final ChannelHandlerContext ctx, String errMsg, Throwable t) {
346         finished = true;
347         ctx.close(); // drop connection immediately per spec
348         HAProxyProtocolException ppex;
349         if (errMsg != null && t != null) {
350             ppex = new HAProxyProtocolException(errMsg, t);
351         } else if (errMsg != null) {
352             ppex = new HAProxyProtocolException(errMsg);
353         } else if (t != null) {
354             ppex = new HAProxyProtocolException(t);
355         } else {
356             ppex = new HAProxyProtocolException();
357         }
358         throw ppex;
359     }
360 }