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.CharsetUtil;
22  import io.netty.util.NetUtil;
23  import io.netty.util.ResourceLeakDetector;
24  import io.netty.util.ResourceLeakDetectorFactory;
25  import io.netty.util.ResourceLeakTracker;
26  import io.netty.util.internal.ObjectUtil;
27  import io.netty.util.internal.PlatformDependent;
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, protocolVersion);
105         checkAddress(destinationAddress, addrFamily, protocolVersion);
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         try {
255             do {
256                 haProxyTLVs.add(haProxyTLV);
257                 if (haProxyTLV instanceof HAProxySSLTLV) {
258                     haProxyTLVs.addAll(((HAProxySSLTLV) haProxyTLV).encapsulatedTLVs());
259                 }
260             } while ((haProxyTLV = readNextTLV(header, 0)) != null);
261         } catch (Throwable t) {
262             // Release all previously read TLVs before rethrowing as otherwise we would leak.
263             releaseTlvs(haProxyTLVs);
264             PlatformDependent.throwException(t);
265         }
266         return haProxyTLVs;
267     }
268 
269     private static void releaseDeep(List<HAProxyTLV> children) {
270         for (HAProxyTLV child : children) {
271             child.release();
272             if (child instanceof HAProxySSLTLV) {
273                 releaseDeep(((HAProxySSLTLV) child).encapsulatedTLVs());
274             }
275         }
276     }
277 
278     private static void releaseTlvs(List<HAProxyTLV> tlvs) {
279         int skip = 0;
280         for (HAProxyTLV tlv : tlvs) {
281             if (skip > 0) {
282                 skip--;
283                 // This TLV is a flattened depth-1 child. If it encapsulates anything (depth-2+),
284                 // those deeper children were NOT flattened, so we must release them recursively.
285                 if (tlv instanceof HAProxySSLTLV) {
286                     releaseDeep(((HAProxySSLTLV) tlv).encapsulatedTLVs());
287                 }
288             } else if (tlv instanceof HAProxySSLTLV) {
289                 // This is a top-level (depth-0) SSL TLV.
290                 // Its immediate children (depth-1) were flattened into this list,
291                 // so we must skip them in the outer loop to avoid treating them as top-level TLVs.
292                 skip = ((HAProxySSLTLV) tlv).encapsulatedTLVs().size();
293             }
294             tlv.release();
295         }
296     }
297 
298     private static HAProxyTLV readNextTLV(final ByteBuf header, int nestingLevel) {
299         if (nestingLevel > MAX_NESTING_LEVEL) {
300             throw new HAProxyProtocolException(
301                     "Maximum TLV nesting level reached: " + nestingLevel + " (expected: < " + MAX_NESTING_LEVEL + ')');
302         }
303         // We need at least 4 bytes for a TLV
304         if (header.readableBytes() < 4) {
305             return null;
306         }
307 
308         final byte typeAsByte = header.readByte();
309         final HAProxyTLV.Type type = HAProxyTLV.Type.typeForByteValue(typeAsByte);
310 
311         final int length = header.readUnsignedShort();
312         switch (type) {
313         case PP2_TYPE_SSL:
314             if (length < 5) {
315                 throw new HAProxyProtocolException("TLV length must be at least 5 but was: " + length);
316             }
317             if (length > header.readableBytes()) {
318                 throw new HAProxyProtocolException("TLV length must be smaller or equal the readable bytes (" +
319                         header.readableBytes() + ") but was: " + length);
320             }
321             // Slice the rawContent but only retain it if we didn't see an error as otherwise we might
322             // leak.
323             final ByteBuf rawContent = header.slice(header.readerIndex(), length);
324             final ByteBuf byteBuf = header.readSlice(length);
325             final byte client = byteBuf.readByte();
326             final int verify = byteBuf.readInt();
327 
328             if (byteBuf.readableBytes() >= 4) {
329 
330                 final List<HAProxyTLV> encapsulatedTlvs = new ArrayList<HAProxyTLV>(4);
331                 try {
332                     do {
333                         final HAProxyTLV haProxyTLV = readNextTLV(byteBuf, nestingLevel + 1);
334                         if (haProxyTLV == null) {
335                             break;
336                         }
337                         encapsulatedTlvs.add(haProxyTLV);
338                     } while (byteBuf.readableBytes() >= 4);
339                 } catch (Throwable t) {
340                     // Release all previously read TLVs before rethrowing as otherwise we would leak.
341                     releaseDeep(encapsulatedTlvs);
342                     PlatformDependent.throwException(t);
343                 }
344 
345                 return new HAProxySSLTLV(verify, client, encapsulatedTlvs, rawContent.retain());
346             }
347             return new HAProxySSLTLV(verify, client, Collections.<HAProxyTLV>emptyList(), rawContent.retain());
348         // If we're not dealing with an SSL Type, we can use the same mechanism
349         case PP2_TYPE_ALPN:
350         case PP2_TYPE_AUTHORITY:
351         case PP2_TYPE_SSL_VERSION:
352         case PP2_TYPE_SSL_CN:
353         case PP2_TYPE_NETNS:
354         case OTHER:
355             return new HAProxyTLV(type, typeAsByte, header.readRetainedSlice(length));
356         default:
357             return null;
358         }
359     }
360 
361     /**
362      * Decodes a version 1, human-readable proxy protocol header.
363      *
364      * @param header                     a version 1 proxy protocol header
365      * @return                           {@link HAProxyMessage} instance
366      * @throws HAProxyProtocolException  if any portion of the header is invalid
367      */
368     static HAProxyMessage decodeHeader(String header) {
369         if (header == null) {
370             throw new HAProxyProtocolException("header");
371         }
372 
373         String[] parts = header.split(" ");
374         int numParts = parts.length;
375 
376         if (numParts < 2) {
377             throw new HAProxyProtocolException(
378                     "invalid header: " + header + " (expected: 'PROXY' and proxied protocol values)");
379         }
380 
381         if (!"PROXY".equals(parts[0])) {
382             throw new HAProxyProtocolException("unknown identifier: " + parts[0]);
383         }
384 
385         HAProxyProxiedProtocol protAndFam;
386         try {
387             protAndFam = HAProxyProxiedProtocol.valueOf(parts[1]);
388         } catch (IllegalArgumentException e) {
389             throw new HAProxyProtocolException(e);
390         }
391 
392         if (protAndFam != HAProxyProxiedProtocol.TCP4 &&
393                 protAndFam != HAProxyProxiedProtocol.TCP6 &&
394                 protAndFam != HAProxyProxiedProtocol.UNKNOWN) {
395             throw new HAProxyProtocolException("unsupported v1 proxied protocol: " + parts[1]);
396         }
397 
398         if (protAndFam == HAProxyProxiedProtocol.UNKNOWN) {
399             return unknownMsg(HAProxyProtocolVersion.V1, HAProxyCommand.PROXY);
400         }
401 
402         if (numParts != 6) {
403             throw new HAProxyProtocolException("invalid TCP4/6 header: " + header + " (expected: 6 parts)");
404         }
405 
406         try {
407             return new HAProxyMessage(
408                     HAProxyProtocolVersion.V1, HAProxyCommand.PROXY,
409                     protAndFam, parts[2], parts[3], parts[4], parts[5]);
410         } catch (RuntimeException e) {
411             throw new HAProxyProtocolException("invalid HAProxy message", e);
412         }
413     }
414 
415     /**
416      * Proxy protocol message for 'UNKNOWN' proxied protocols. Per spec, when the proxied protocol is
417      * 'UNKNOWN' we must discard all other header values.
418      */
419     private static HAProxyMessage unknownMsg(HAProxyProtocolVersion version, HAProxyCommand command) {
420         return new HAProxyMessage(version, command, HAProxyProxiedProtocol.UNKNOWN, null, null, 0, 0);
421     }
422 
423     /**
424      * Convert ip address bytes to string representation
425      *
426      * @param header     buffer containing ip address bytes
427      * @param addressLen number of bytes to read (4 bytes for IPv4, 16 bytes for IPv6)
428      * @return           string representation of the ip address
429      */
430     private static String ipBytesToString(ByteBuf header, int addressLen) {
431         StringBuilder sb = new StringBuilder();
432         final int ipv4Len = 4;
433         final int ipv6Len = 8;
434         if (addressLen == ipv4Len) {
435             for (int i = 0; i < ipv4Len; i++) {
436                 sb.append(header.readByte() & 0xff);
437                 sb.append('.');
438             }
439         } else {
440             for (int i = 0; i < ipv6Len; i++) {
441                 sb.append(Integer.toHexString(header.readUnsignedShort()));
442                 sb.append(':');
443             }
444         }
445         sb.setLength(sb.length() - 1);
446         return sb.toString();
447     }
448 
449     /**
450      * Convert port to integer
451      *
452      * @param value                      the port
453      * @return                           port as an integer
454      * @throws IllegalArgumentException  if port is not a valid integer
455      */
456     private static int portStringToInt(String value) {
457         int port;
458         try {
459             port = Integer.parseInt(value);
460         } catch (NumberFormatException e) {
461             throw new IllegalArgumentException("invalid port: " + value, e);
462         }
463 
464         if (port <= 0 || port > 65535) {
465             throw new IllegalArgumentException("invalid port: " + value + " (expected: 1 ~ 65535)");
466         }
467 
468         return port;
469     }
470 
471     /**
472      * Validate an address (IPv4, IPv6, Unix Socket)
473      *
474      * @param address    human-readable address
475      * @param addrFamily the {@link AddressFamily} to check the address against
476      * @param version    the protocol version
477      * @throws IllegalArgumentException if the address is invalid
478      */
479     private static void checkAddress(String address, AddressFamily addrFamily, HAProxyProtocolVersion version) {
480         ObjectUtil.checkNotNull(addrFamily, "addrFamily");
481 
482         switch (addrFamily) {
483             case AF_UNSPEC:
484                 if (address != null) {
485                     throw new IllegalArgumentException("unable to validate an AF_UNSPEC address: " + address);
486                 }
487                 return;
488             case AF_UNIX:
489                 ObjectUtil.checkNotNull(address, "address");
490                 if (address.getBytes(CharsetUtil.US_ASCII).length > 108) {
491                     throw new IllegalArgumentException("invalid AF_UNIX address: " + address);
492                 }
493                 if (version == HAProxyProtocolVersion.V1) {
494                     // V1 is text-based and uses CR LF as header delimiters, and space as field delimiter.
495                     for (int i = 0, len = address.length(); i < len; i++) {
496                         char c = address.charAt(i);
497                         if (c == '\r' || c == '\n' || c == ' ') {
498                             throw new IllegalArgumentException("invalid AF_UNIX address: " + address);
499                         }
500                     }
501                 }
502                 return;
503         }
504 
505         ObjectUtil.checkNotNull(address, "address");
506 
507         switch (addrFamily) {
508             case AF_IPv4:
509                 if (!NetUtil.isValidIpV4Address(address)) {
510                     throw new IllegalArgumentException("invalid IPv4 address: " + address);
511                 }
512                 break;
513             case AF_IPv6:
514                 if (!NetUtil.isValidIpV6Address(address)) {
515                     throw new IllegalArgumentException("invalid IPv6 address: " + address);
516                 }
517                 break;
518             default:
519                 throw new IllegalArgumentException("unexpected addrFamily: " + addrFamily);
520         }
521     }
522 
523     /**
524      * Validate the port depending on the addrFamily.
525      *
526      * @param port                       the UDP/TCP port
527      * @throws IllegalArgumentException  if the port is out of range (0-65535 inclusive)
528      */
529     private static void checkPort(int port, AddressFamily addrFamily) {
530         switch (addrFamily) {
531         case AF_IPv6:
532         case AF_IPv4:
533             if (port < 0 || port > 65535) {
534                 throw new IllegalArgumentException("invalid port: " + port + " (expected: 0 ~ 65535)");
535             }
536             break;
537         case AF_UNIX:
538         case AF_UNSPEC:
539             if (port != 0) {
540                 throw new IllegalArgumentException("port cannot be specified with addrFamily: " + addrFamily);
541             }
542             break;
543         default:
544             throw new IllegalArgumentException("unexpected addrFamily: " + addrFamily);
545         }
546     }
547 
548     /**
549      * Returns the {@link HAProxyProtocolVersion} of this {@link HAProxyMessage}.
550      */
551     public HAProxyProtocolVersion protocolVersion() {
552         return protocolVersion;
553     }
554 
555     /**
556      * Returns the {@link HAProxyCommand} of this {@link HAProxyMessage}.
557      */
558     public HAProxyCommand command() {
559         return command;
560     }
561 
562     /**
563      * Returns the {@link HAProxyProxiedProtocol} of this {@link HAProxyMessage}.
564      */
565     public HAProxyProxiedProtocol proxiedProtocol() {
566         return proxiedProtocol;
567     }
568 
569     /**
570      * Returns the human-readable source address of this {@link HAProxyMessage} or {@code null}
571      * if HAProxy performs health check with {@code send-proxy-v2}.
572      */
573     public String sourceAddress() {
574         return sourceAddress;
575     }
576 
577     /**
578      * Returns the human-readable destination address of this {@link HAProxyMessage}.
579      */
580     public String destinationAddress() {
581         return destinationAddress;
582     }
583 
584     /**
585      * Returns the UDP/TCP source port of this {@link HAProxyMessage}.
586      */
587     public int sourcePort() {
588         return sourcePort;
589     }
590 
591     /**
592      * Returns the UDP/TCP destination port of this {@link HAProxyMessage}.
593      */
594     public int destinationPort() {
595         return destinationPort;
596     }
597 
598     /**
599      * Returns a list of {@link HAProxyTLV} or an empty list if no TLVs are present.
600      * <p>
601      * TLVs are only available for the Proxy Protocol V2
602      */
603     public List<HAProxyTLV> tlvs() {
604         return tlvs;
605     }
606 
607     int tlvNumBytes() {
608         int tlvNumBytes = 0;
609         for (int i = 0; i < tlvs.size(); i++) {
610             tlvNumBytes += tlvs.get(i).totalNumBytes();
611         }
612         return tlvNumBytes;
613     }
614 
615     @Override
616     public HAProxyMessage touch() {
617         tryRecord();
618         return (HAProxyMessage) super.touch();
619     }
620 
621     @Override
622     public HAProxyMessage touch(Object hint) {
623         if (leak != null) {
624             leak.record(hint);
625         }
626         return this;
627     }
628 
629     @Override
630     public HAProxyMessage retain() {
631         tryRecord();
632         return (HAProxyMessage) super.retain();
633     }
634 
635     @Override
636     public HAProxyMessage retain(int increment) {
637         tryRecord();
638         return (HAProxyMessage) super.retain(increment);
639     }
640 
641     @Override
642     public boolean release() {
643         tryRecord();
644         return super.release();
645     }
646 
647     @Override
648     public boolean release(int decrement) {
649         tryRecord();
650         return super.release(decrement);
651     }
652 
653     private void tryRecord() {
654         if (leak != null) {
655             leak.record();
656         }
657     }
658 
659     @Override
660     protected void deallocate() {
661         try {
662             releaseTlvs(tlvs);
663         } finally {
664             final ResourceLeakTracker<HAProxyMessage> leak = this.leak;
665             if (leak != null) {
666                 boolean closed = leak.close(this);
667                 assert closed;
668             }
669         }
670     }
671 
672     @Override
673     public String toString() {
674         StringBuilder sb = new StringBuilder(256)
675                 .append(StringUtil.simpleClassName(this))
676                 .append("(protocolVersion: ").append(protocolVersion)
677                 .append(", command: ").append(command)
678                 .append(", proxiedProtocol: ").append(proxiedProtocol)
679                 .append(", sourceAddress: ").append(sourceAddress)
680                 .append(", destinationAddress: ").append(destinationAddress)
681                 .append(", sourcePort: ").append(sourcePort)
682                 .append(", destinationPort: ").append(destinationPort)
683                 .append(", tlvs: [");
684         if (!tlvs.isEmpty()) {
685             for (HAProxyTLV tlv: tlvs) {
686                 sb.append(tlv).append(", ");
687             }
688             sb.setLength(sb.length() - 2);
689         }
690         sb.append("])");
691         return sb.toString();
692     }
693 }