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.buffer.ByteBufProcessor;
20  import io.netty.handler.codec.haproxy.HAProxyProxiedProtocol.AddressFamily;
21  import io.netty.util.CharsetUtil;
22  import io.netty.util.NetUtil;
23  
24  import java.util.ArrayList;
25  import java.util.Collections;
26  import java.util.List;
27  
28  /**
29   * Message container for decoded HAProxy proxy protocol parameters
30   */
31  public final class HAProxyMessage {
32  
33      /**
34       * Version 1 proxy protocol message for 'UNKNOWN' proxied protocols. Per spec, when the proxied protocol is
35       * 'UNKNOWN' we must discard all other header values.
36       */
37      private static final HAProxyMessage V1_UNKNOWN_MSG = new HAProxyMessage(
38              HAProxyProtocolVersion.V1, HAProxyCommand.PROXY, HAProxyProxiedProtocol.UNKNOWN, null, null, 0, 0);
39  
40      /**
41       * Version 2 proxy protocol message for 'UNKNOWN' proxied protocols. Per spec, when the proxied protocol is
42       * 'UNKNOWN' we must discard all other header values.
43       */
44      private static final HAProxyMessage V2_UNKNOWN_MSG = new HAProxyMessage(
45              HAProxyProtocolVersion.V2, HAProxyCommand.PROXY, HAProxyProxiedProtocol.UNKNOWN, null, null, 0, 0);
46  
47      /**
48       * Version 2 proxy protocol message for local requests. Per spec, we should use an unspecified protocol and family
49       * for 'LOCAL' commands. Per spec, when the proxied protocol is 'UNKNOWN' we must discard all other header values.
50       */
51      private static final HAProxyMessage V2_LOCAL_MSG = new HAProxyMessage(
52              HAProxyProtocolVersion.V2, HAProxyCommand.LOCAL, HAProxyProxiedProtocol.UNKNOWN, null, null, 0, 0);
53  
54      private final HAProxyProtocolVersion protocolVersion;
55      private final HAProxyCommand command;
56      private final HAProxyProxiedProtocol proxiedProtocol;
57      private final String sourceAddress;
58      private final String destinationAddress;
59      private final int sourcePort;
60      private final int destinationPort;
61      private final List<HAProxyTLV> tlvs;
62  
63      /**
64       * Creates a new instance
65       */
66      private HAProxyMessage(
67              HAProxyProtocolVersion protocolVersion, HAProxyCommand command, HAProxyProxiedProtocol proxiedProtocol,
68              String sourceAddress, String destinationAddress, String sourcePort, String destinationPort) {
69          this(
70                  protocolVersion, command, proxiedProtocol,
71                  sourceAddress, destinationAddress, portStringToInt(sourcePort), portStringToInt(destinationPort));
72      }
73  
74      /**
75       * Creates a new instance
76       */
77      private HAProxyMessage(
78              HAProxyProtocolVersion protocolVersion, HAProxyCommand command, HAProxyProxiedProtocol proxiedProtocol,
79              String sourceAddress, String destinationAddress, int sourcePort, int destinationPort) {
80  
81          this(protocolVersion, command, proxiedProtocol,
82               sourceAddress, destinationAddress, sourcePort, destinationPort, Collections.<HAProxyTLV>emptyList());
83      }
84  
85      /**
86       * Creates a new instance
87       */
88      private HAProxyMessage(
89              HAProxyProtocolVersion protocolVersion, HAProxyCommand command, HAProxyProxiedProtocol proxiedProtocol,
90              String sourceAddress, String destinationAddress, int sourcePort, int destinationPort,
91              List<HAProxyTLV> tlvs) {
92  
93          if (proxiedProtocol == null) {
94              throw new NullPointerException("proxiedProtocol");
95          }
96          AddressFamily addrFamily = proxiedProtocol.addressFamily();
97  
98          checkAddress(sourceAddress, addrFamily);
99          checkAddress(destinationAddress, addrFamily);
100         checkPort(sourcePort);
101         checkPort(destinationPort);
102 
103         this.protocolVersion = protocolVersion;
104         this.command = command;
105         this.proxiedProtocol = proxiedProtocol;
106         this.sourceAddress = sourceAddress;
107         this.destinationAddress = destinationAddress;
108         this.sourcePort = sourcePort;
109         this.destinationPort = destinationPort;
110         this.tlvs = Collections.unmodifiableList(tlvs);
111     }
112 
113     /**
114      * Decodes a version 2, binary proxy protocol header.
115      *
116      * @param header                     a version 2 proxy protocol header
117      * @return                           {@link HAProxyMessage} instance
118      * @throws HAProxyProtocolException  if any portion of the header is invalid
119      */
120     static HAProxyMessage decodeHeader(ByteBuf header) {
121         if (header == null) {
122             throw new NullPointerException("header");
123         }
124 
125         if (header.readableBytes() < 16) {
126             throw new HAProxyProtocolException(
127                     "incomplete header: " + header.readableBytes() + " bytes (expected: 16+ bytes)");
128         }
129 
130         // Per spec, the 13th byte is the protocol version and command byte
131         header.skipBytes(12);
132         final byte verCmdByte = header.readByte();
133 
134         HAProxyProtocolVersion ver;
135         try {
136             ver = HAProxyProtocolVersion.valueOf(verCmdByte);
137         } catch (IllegalArgumentException e) {
138             throw new HAProxyProtocolException(e);
139         }
140 
141         if (ver != HAProxyProtocolVersion.V2) {
142             throw new HAProxyProtocolException("version 1 unsupported: 0x" + Integer.toHexString(verCmdByte));
143         }
144 
145         HAProxyCommand cmd;
146         try {
147             cmd = HAProxyCommand.valueOf(verCmdByte);
148         } catch (IllegalArgumentException e) {
149             throw new HAProxyProtocolException(e);
150         }
151 
152         if (cmd == HAProxyCommand.LOCAL) {
153             return V2_LOCAL_MSG;
154         }
155 
156         // Per spec, the 14th byte is the protocol and address family byte
157         HAProxyProxiedProtocol protAndFam;
158         try {
159             protAndFam = HAProxyProxiedProtocol.valueOf(header.readByte());
160         } catch (IllegalArgumentException e) {
161             throw new HAProxyProtocolException(e);
162         }
163 
164         if (protAndFam == HAProxyProxiedProtocol.UNKNOWN) {
165             return V2_UNKNOWN_MSG;
166         }
167 
168         int addressInfoLen = header.readUnsignedShort();
169 
170         String srcAddress;
171         String dstAddress;
172         int addressLen;
173         int srcPort = 0;
174         int dstPort = 0;
175 
176         AddressFamily addressFamily = protAndFam.addressFamily();
177 
178         if (addressFamily == AddressFamily.AF_UNIX) {
179             // unix sockets require 216 bytes for address information
180             if (addressInfoLen < 216 || header.readableBytes() < 216) {
181                 throw new HAProxyProtocolException(
182                     "incomplete UNIX socket address information: " +
183                             Math.min(addressInfoLen, header.readableBytes()) + " bytes (expected: 216+ bytes)");
184             }
185             int startIdx = header.readerIndex();
186             int addressEnd = header.forEachByte(startIdx, 108, ByteBufProcessor.FIND_NUL);
187             if (addressEnd == -1) {
188                 addressLen = 108;
189             } else {
190                 addressLen = addressEnd - startIdx;
191             }
192             srcAddress = header.toString(startIdx, addressLen, CharsetUtil.US_ASCII);
193 
194             startIdx += 108;
195 
196             addressEnd = header.forEachByte(startIdx, 108, ByteBufProcessor.FIND_NUL);
197             if (addressEnd == -1) {
198                 addressLen = 108;
199             } else {
200                 addressLen = addressEnd - startIdx;
201             }
202             dstAddress = header.toString(startIdx, addressLen, CharsetUtil.US_ASCII);
203             // AF_UNIX defines that exactly 108 bytes are reserved for the address. The previous methods
204             // did not increase the reader index although we already consumed the information.
205             header.readerIndex(startIdx + 108);
206         } else {
207             if (addressFamily == AddressFamily.AF_IPv4) {
208                 // IPv4 requires 12 bytes for address information
209                 if (addressInfoLen < 12 || header.readableBytes() < 12) {
210                     throw new HAProxyProtocolException(
211                         "incomplete IPv4 address information: " +
212                                 Math.min(addressInfoLen, header.readableBytes()) + " bytes (expected: 12+ bytes)");
213                 }
214                 addressLen = 4;
215             } else if (addressFamily == AddressFamily.AF_IPv6) {
216                 // IPv6 requires 36 bytes for address information
217                 if (addressInfoLen < 36 || header.readableBytes() < 36) {
218                     throw new HAProxyProtocolException(
219                         "incomplete IPv6 address information: " +
220                                 Math.min(addressInfoLen, header.readableBytes()) + " bytes (expected: 36+ bytes)");
221                 }
222                 addressLen = 16;
223             } else {
224                 throw new HAProxyProtocolException(
225                     "unable to parse address information (unknown address family: " + addressFamily + ')');
226             }
227 
228             // Per spec, the src address begins at the 17th byte
229             srcAddress = ipBytesToString(header, addressLen);
230             dstAddress = ipBytesToString(header, addressLen);
231             srcPort = header.readUnsignedShort();
232             dstPort = header.readUnsignedShort();
233         }
234 
235         final List<HAProxyTLV> tlvs = readTlvs(header);
236 
237         return new HAProxyMessage(ver, cmd, protAndFam, srcAddress, dstAddress, srcPort, dstPort, tlvs);
238     }
239 
240     private static List<HAProxyTLV> readTlvs(final ByteBuf header) {
241         HAProxyTLV haProxyTLV = readNextTLV(header);
242         if (haProxyTLV == null) {
243             return Collections.emptyList();
244         }
245         // In most cases there are less than 4 TLVs available
246         List<HAProxyTLV> haProxyTLVs = new ArrayList<HAProxyTLV>(4);
247 
248         do {
249             haProxyTLVs.add(haProxyTLV);
250             if (haProxyTLV instanceof HAProxySSLTLV) {
251                 haProxyTLVs.addAll(((HAProxySSLTLV) haProxyTLV).encapsulatedTLVs());
252             }
253         } while ((haProxyTLV = readNextTLV(header)) != null);
254         return haProxyTLVs;
255     }
256 
257     private static HAProxyTLV readNextTLV(final ByteBuf header) {
258 
259         // We need at least 4 bytes for a TLV
260         if (header.readableBytes() < 4) {
261             return null;
262         }
263 
264         final byte typeAsByte = header.readByte();
265         final HAProxyTLV.Type type = HAProxyTLV.Type.typeForByteValue(typeAsByte);
266 
267         final int length = header.readUnsignedShort();
268         switch (type) {
269         case PP2_TYPE_SSL:
270             final ByteBuf rawContent = header.slice(header.readerIndex(), length).retain();
271             final ByteBuf byteBuf = header.readSlice(length);
272             final byte client = byteBuf.readByte();
273             final int verify = byteBuf.readInt();
274 
275             if (byteBuf.readableBytes() >= 4) {
276 
277                 final List<HAProxyTLV> encapsulatedTlvs = new ArrayList<HAProxyTLV>(4);
278                 do {
279                     final HAProxyTLV haProxyTLV = readNextTLV(byteBuf);
280                     if (haProxyTLV == null) {
281                         break;
282                     }
283                     encapsulatedTlvs.add(haProxyTLV);
284                 } while (byteBuf.readableBytes() >= 4);
285 
286                 return new HAProxySSLTLV(verify, client, encapsulatedTlvs, rawContent);
287             }
288             return new HAProxySSLTLV(verify, client, Collections.<HAProxyTLV>emptyList(), rawContent);
289         // If we're not dealing with a SSL Type, we can use the same mechanism
290         case PP2_TYPE_ALPN:
291         case PP2_TYPE_AUTHORITY:
292         case PP2_TYPE_SSL_VERSION:
293         case PP2_TYPE_SSL_CN:
294         case PP2_TYPE_NETNS:
295         case OTHER:
296             return new HAProxyTLV(type, typeAsByte, header.readSlice(length).retain());
297         default:
298             return null;
299         }
300     }
301 
302     /**
303      * Decodes a version 1, human-readable proxy protocol header.
304      *
305      * @param header                     a version 1 proxy protocol header
306      * @return                           {@link HAProxyMessage} instance
307      * @throws HAProxyProtocolException  if any portion of the header is invalid
308      */
309     static HAProxyMessage decodeHeader(String header) {
310         if (header == null) {
311             throw new HAProxyProtocolException("header");
312         }
313 
314         String[] parts = header.split(" ");
315         int numParts = parts.length;
316 
317         if (numParts < 2) {
318             throw new HAProxyProtocolException(
319                     "invalid header: " + header + " (expected: 'PROXY' and proxied protocol values)");
320         }
321 
322         if (!"PROXY".equals(parts[0])) {
323             throw new HAProxyProtocolException("unknown identifier: " + parts[0]);
324         }
325 
326         HAProxyProxiedProtocol protAndFam;
327         try {
328             protAndFam = HAProxyProxiedProtocol.valueOf(parts[1]);
329         } catch (IllegalArgumentException e) {
330             throw new HAProxyProtocolException(e);
331         }
332 
333         if (protAndFam != HAProxyProxiedProtocol.TCP4 &&
334                 protAndFam != HAProxyProxiedProtocol.TCP6 &&
335                 protAndFam != HAProxyProxiedProtocol.UNKNOWN) {
336             throw new HAProxyProtocolException("unsupported v1 proxied protocol: " + parts[1]);
337         }
338 
339         if (protAndFam == HAProxyProxiedProtocol.UNKNOWN) {
340             return V1_UNKNOWN_MSG;
341         }
342 
343         if (numParts != 6) {
344             throw new HAProxyProtocolException("invalid TCP4/6 header: " + header + " (expected: 6 parts)");
345         }
346 
347         return new HAProxyMessage(
348                 HAProxyProtocolVersion.V1, HAProxyCommand.PROXY,
349                 protAndFam, parts[2], parts[3], parts[4], parts[5]);
350     }
351 
352     /**
353      * Convert ip address bytes to string representation
354      *
355      * @param header     buffer containing ip address bytes
356      * @param addressLen number of bytes to read (4 bytes for IPv4, 16 bytes for IPv6)
357      * @return           string representation of the ip address
358      */
359     private static String ipBytesToString(ByteBuf header, int addressLen) {
360         StringBuilder sb = new StringBuilder();
361         if (addressLen == 4) {
362             sb.append(header.readByte() & 0xff);
363             sb.append('.');
364             sb.append(header.readByte() & 0xff);
365             sb.append('.');
366             sb.append(header.readByte() & 0xff);
367             sb.append('.');
368             sb.append(header.readByte() & 0xff);
369         } else {
370             sb.append(Integer.toHexString(header.readUnsignedShort()));
371             sb.append(':');
372             sb.append(Integer.toHexString(header.readUnsignedShort()));
373             sb.append(':');
374             sb.append(Integer.toHexString(header.readUnsignedShort()));
375             sb.append(':');
376             sb.append(Integer.toHexString(header.readUnsignedShort()));
377             sb.append(':');
378             sb.append(Integer.toHexString(header.readUnsignedShort()));
379             sb.append(':');
380             sb.append(Integer.toHexString(header.readUnsignedShort()));
381             sb.append(':');
382             sb.append(Integer.toHexString(header.readUnsignedShort()));
383             sb.append(':');
384             sb.append(Integer.toHexString(header.readUnsignedShort()));
385         }
386         return sb.toString();
387     }
388 
389     /**
390      * Convert port to integer
391      *
392      * @param value                      the port
393      * @return                           port as an integer
394      * @throws HAProxyProtocolException  if port is not a valid integer
395      */
396     private static int portStringToInt(String value) {
397         int port;
398         try {
399             port = Integer.parseInt(value);
400         } catch (NumberFormatException e) {
401             throw new HAProxyProtocolException("invalid port: " + value, e);
402         }
403 
404         if (port <= 0 || port > 65535) {
405             throw new HAProxyProtocolException("invalid port: " + value + " (expected: 1 ~ 65535)");
406         }
407 
408         return port;
409     }
410 
411     /**
412      * Validate an address (IPv4, IPv6, Unix Socket)
413      *
414      * @param address                    human-readable address
415      * @param addrFamily                 the {@link AddressFamily} to check the address against
416      * @throws HAProxyProtocolException  if the address is invalid
417      */
418     private static void checkAddress(String address, AddressFamily addrFamily) {
419         if (addrFamily == null) {
420             throw new NullPointerException("addrFamily");
421         }
422 
423         switch (addrFamily) {
424             case AF_UNSPEC:
425                 if (address != null) {
426                     throw new HAProxyProtocolException("unable to validate an AF_UNSPEC address: " + address);
427                 }
428                 return;
429             case AF_UNIX:
430                 return;
431         }
432 
433         if (address == null) {
434             throw new NullPointerException("address");
435         }
436 
437         switch (addrFamily) {
438             case AF_IPv4:
439                 if (!NetUtil.isValidIpV4Address(address)) {
440                     throw new HAProxyProtocolException("invalid IPv4 address: " + address);
441                 }
442                 break;
443             case AF_IPv6:
444                 if (!NetUtil.isValidIpV6Address(address)) {
445                     throw new HAProxyProtocolException("invalid IPv6 address: " + address);
446                 }
447                 break;
448             default:
449                 throw new Error();
450         }
451     }
452 
453     /**
454      * Validate a UDP/TCP port
455      *
456      * @param port                       the UDP/TCP port
457      * @throws HAProxyProtocolException  if the port is out of range (0-65535 inclusive)
458      */
459     private static void checkPort(int port) {
460         if (port < 0 || port > 65535) {
461             throw new HAProxyProtocolException("invalid port: " + port + " (expected: 1 ~ 65535)");
462         }
463     }
464 
465     /**
466      * Returns the {@link HAProxyProtocolVersion} of this {@link HAProxyMessage}.
467      */
468     public HAProxyProtocolVersion protocolVersion() {
469         return protocolVersion;
470     }
471 
472     /**
473      * Returns the {@link HAProxyCommand} of this {@link HAProxyMessage}.
474      */
475     public HAProxyCommand command() {
476         return command;
477     }
478 
479     /**
480      * Returns the {@link HAProxyProxiedProtocol} of this {@link HAProxyMessage}.
481      */
482     public HAProxyProxiedProtocol proxiedProtocol() {
483         return proxiedProtocol;
484     }
485 
486     /**
487      * Returns the human-readable source address of this {@link HAProxyMessage}.
488      */
489     public String sourceAddress() {
490         return sourceAddress;
491     }
492 
493     /**
494      * Returns the human-readable destination address of this {@link HAProxyMessage}.
495      */
496     public String destinationAddress() {
497         return destinationAddress;
498     }
499 
500     /**
501      * Returns the UDP/TCP source port of this {@link HAProxyMessage}.
502      */
503     public int sourcePort() {
504         return sourcePort;
505     }
506 
507     /**
508      * Returns the UDP/TCP destination port of this {@link HAProxyMessage}.
509      */
510     public int destinationPort() {
511         return destinationPort;
512     }
513 
514     /**
515      * Returns a list of {@link HAProxyTLV} or an empty list if no TLVs are present.
516      * <p>
517      * TLVs are only available for the Proxy Protocol V2
518      */
519     public List<HAProxyTLV> tlvs() {
520         return tlvs;
521     }
522 }