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