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