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  import io.netty.util.internal.StringUtil;
24  
25  /**
26   * Message container for decoded HAProxy proxy protocol parameters
27   */
28  public final class HAProxyMessage {
29  
30      /**
31       * Version 1 proxy protocol message for 'UNKNOWN' proxied protocols. Per spec, when the proxied protocol is
32       * 'UNKNOWN' we must discard all other header values.
33       */
34      private static final HAProxyMessage V1_UNKNOWN_MSG = new HAProxyMessage(
35              HAProxyProtocolVersion.V1, HAProxyCommand.PROXY, HAProxyProxiedProtocol.UNKNOWN, null, null, 0, 0);
36  
37      /**
38       * Version 2 proxy protocol message for 'UNKNOWN' proxied protocols. Per spec, when the proxied protocol is
39       * 'UNKNOWN' we must discard all other header values.
40       */
41      private static final HAProxyMessage V2_UNKNOWN_MSG = new HAProxyMessage(
42              HAProxyProtocolVersion.V2, HAProxyCommand.PROXY, HAProxyProxiedProtocol.UNKNOWN, null, null, 0, 0);
43  
44      /**
45       * Version 2 proxy protocol message for local requests. Per spec, we should use an unspecified protocol and family
46       * for 'LOCAL' commands. Per spec, when the proxied protocol is 'UNKNOWN' we must discard all other header values.
47       */
48      private static final HAProxyMessage V2_LOCAL_MSG = new HAProxyMessage(
49              HAProxyProtocolVersion.V2, HAProxyCommand.LOCAL, HAProxyProxiedProtocol.UNKNOWN, null, null, 0, 0);
50  
51      private final HAProxyProtocolVersion protocolVersion;
52      private final HAProxyCommand command;
53      private final HAProxyProxiedProtocol proxiedProtocol;
54      private final String sourceAddress;
55      private final String destinationAddress;
56      private final int sourcePort;
57      private final int destinationPort;
58  
59      /**
60       * Creates a new instance
61       */
62      private HAProxyMessage(
63              HAProxyProtocolVersion protocolVersion, HAProxyCommand command, HAProxyProxiedProtocol proxiedProtocol,
64              String sourceAddress, String destinationAddress, String sourcePort, String destinationPort) {
65          this(
66                  protocolVersion, command, proxiedProtocol,
67                  sourceAddress, destinationAddress, portStringToInt(sourcePort), portStringToInt(destinationPort));
68      }
69  
70      /**
71       * Creates a new instance
72       */
73      private HAProxyMessage(
74              HAProxyProtocolVersion protocolVersion, HAProxyCommand command, HAProxyProxiedProtocol proxiedProtocol,
75              String sourceAddress, String destinationAddress, int sourcePort, int destinationPort) {
76  
77          if (proxiedProtocol == null) {
78              throw new NullPointerException("proxiedProtocol");
79          }
80          AddressFamily addrFamily = proxiedProtocol.addressFamily();
81  
82          checkAddress(sourceAddress, addrFamily);
83          checkAddress(destinationAddress, addrFamily);
84          checkPort(sourcePort);
85          checkPort(destinationPort);
86  
87          this.protocolVersion = protocolVersion;
88          this.command = command;
89          this.proxiedProtocol = proxiedProtocol;
90          this.sourceAddress = sourceAddress;
91          this.destinationAddress = destinationAddress;
92          this.sourcePort = sourcePort;
93          this.destinationPort = destinationPort;
94      }
95  
96      /**
97       * Decodes a version 2, binary proxy protocol header.
98       *
99       * @param header                     a version 2 proxy protocol header
100      * @return                           {@link HAProxyMessage} instance
101      * @throws HAProxyProtocolException  if any portion of the header is invalid
102      */
103     static HAProxyMessage decodeHeader(ByteBuf header) {
104         if (header == null) {
105             throw new NullPointerException("header");
106         }
107 
108         if (header.readableBytes() < 16) {
109             throw new HAProxyProtocolException(
110                     "incomplete header: " + header.readableBytes() + " bytes (expected: 16+ bytes)");
111         }
112 
113         // Per spec, the 13th byte is the protocol version and command byte
114         header.skipBytes(12);
115         final byte verCmdByte = header.readByte();
116 
117         HAProxyProtocolVersion ver;
118         try {
119             ver = HAProxyProtocolVersion.valueOf(verCmdByte);
120         } catch (IllegalArgumentException e) {
121             throw new HAProxyProtocolException(e);
122         }
123 
124         if (ver != HAProxyProtocolVersion.V2) {
125             throw new HAProxyProtocolException("version 1 unsupported: 0x" + Integer.toHexString(verCmdByte));
126         }
127 
128         HAProxyCommand cmd;
129         try {
130             cmd = HAProxyCommand.valueOf(verCmdByte);
131         } catch (IllegalArgumentException e) {
132             throw new HAProxyProtocolException(e);
133         }
134 
135         if (cmd == HAProxyCommand.LOCAL) {
136             return V2_LOCAL_MSG;
137         }
138 
139         // Per spec, the 14th byte is the protocol and address family byte
140         HAProxyProxiedProtocol protAndFam;
141         try {
142             protAndFam = HAProxyProxiedProtocol.valueOf(header.readByte());
143         } catch (IllegalArgumentException e) {
144             throw new HAProxyProtocolException(e);
145         }
146 
147         if (protAndFam == HAProxyProxiedProtocol.UNKNOWN) {
148             return V2_UNKNOWN_MSG;
149         }
150 
151         int addressInfoLen = header.readUnsignedShort();
152 
153         String srcAddress;
154         String dstAddress;
155         int addressLen;
156         int srcPort = 0;
157         int dstPort = 0;
158 
159         AddressFamily addressFamily = protAndFam.addressFamily();
160 
161         if (addressFamily == AddressFamily.AF_UNIX) {
162             // unix sockets require 216 bytes for address information
163             if (addressInfoLen < 216 || header.readableBytes() < 216) {
164                 throw new HAProxyProtocolException(
165                     "incomplete UNIX socket address information: " +
166                             Math.min(addressInfoLen, header.readableBytes()) + " bytes (expected: 216+ bytes)");
167             }
168             int startIdx = header.readerIndex();
169             int addressEnd = header.forEachByte(startIdx, 108, ByteBufProcessor.FIND_NUL);
170             if (addressEnd == -1) {
171                 addressLen = 108;
172             } else {
173                 addressLen = addressEnd - startIdx;
174             }
175             srcAddress = header.toString(startIdx, addressLen, CharsetUtil.US_ASCII);
176 
177             startIdx += 108;
178 
179             addressEnd = header.forEachByte(startIdx, 108, ByteBufProcessor.FIND_NUL);
180             if (addressEnd == -1) {
181                 addressLen = 108;
182             } else {
183                 addressLen = addressEnd - startIdx;
184             }
185             dstAddress = header.toString(startIdx, addressLen, CharsetUtil.US_ASCII);
186         } else {
187             if (addressFamily == AddressFamily.AF_IPv4) {
188                 // IPv4 requires 12 bytes for address information
189                 if (addressInfoLen < 12 || header.readableBytes() < 12) {
190                     throw new HAProxyProtocolException(
191                         "incomplete IPv4 address information: " +
192                                 Math.min(addressInfoLen, header.readableBytes()) + " bytes (expected: 12+ bytes)");
193                 }
194                 addressLen = 4;
195             } else if (addressFamily == AddressFamily.AF_IPv6) {
196                 // IPv6 requires 36 bytes for address information
197                 if (addressInfoLen < 36 || header.readableBytes() < 36) {
198                     throw new HAProxyProtocolException(
199                         "incomplete IPv6 address information: " +
200                                 Math.min(addressInfoLen, header.readableBytes()) + " bytes (expected: 36+ bytes)");
201                 }
202                 addressLen = 16;
203             } else {
204                 throw new HAProxyProtocolException(
205                     "unable to parse address information (unkown address family: " + addressFamily + ')');
206             }
207 
208             // Per spec, the src address begins at the 17th byte
209             srcAddress = ipBytestoString(header, addressLen);
210             dstAddress = ipBytestoString(header, addressLen);
211             srcPort = header.readUnsignedShort();
212             dstPort = header.readUnsignedShort();
213         }
214 
215         return new HAProxyMessage(ver, cmd, protAndFam, srcAddress, dstAddress, srcPort, dstPort);
216     }
217 
218     /**
219      * Decodes a version 1, human-readable proxy protocol header.
220      *
221      * @param header                     a version 1 proxy protocol header
222      * @return                           {@link HAProxyMessage} instance
223      * @throws HAProxyProtocolException  if any portion of the header is invalid
224      */
225     static HAProxyMessage decodeHeader(String header) {
226         if (header == null) {
227             throw new HAProxyProtocolException("header");
228         }
229 
230         String[] parts = StringUtil.split(header, ' ');
231         int numParts = parts.length;
232 
233         if (numParts < 2) {
234             throw new HAProxyProtocolException(
235                     "invalid header: " + header + " (expected: 'PROXY' and proxied protocol values)");
236         }
237 
238         if (!"PROXY".equals(parts[0])) {
239             throw new HAProxyProtocolException("unknown identifier: " + parts[0]);
240         }
241 
242         HAProxyProxiedProtocol protAndFam;
243         try {
244             protAndFam = HAProxyProxiedProtocol.valueOf(parts[1]);
245         } catch (IllegalArgumentException e) {
246             throw new HAProxyProtocolException(e);
247         }
248 
249         if (protAndFam != HAProxyProxiedProtocol.TCP4 &&
250                 protAndFam != HAProxyProxiedProtocol.TCP6 &&
251                 protAndFam != HAProxyProxiedProtocol.UNKNOWN) {
252             throw new HAProxyProtocolException("unsupported v1 proxied protocol: " + parts[1]);
253         }
254 
255         if (protAndFam == HAProxyProxiedProtocol.UNKNOWN) {
256             return V1_UNKNOWN_MSG;
257         }
258 
259         if (numParts != 6) {
260             throw new HAProxyProtocolException("invalid TCP4/6 header: " + header + " (expected: 6 parts)");
261         }
262 
263         return new HAProxyMessage(
264                 HAProxyProtocolVersion.V1, HAProxyCommand.PROXY,
265                 protAndFam, parts[2], parts[3], parts[4], parts[5]);
266     }
267 
268     /**
269      * Convert ip address bytes to string representation
270      *
271      * @param header     buffer containing ip address bytes
272      * @param addressLen number of bytes to read (4 bytes for IPv4, 16 bytes for IPv6)
273      * @return           string representation of the ip address
274      */
275     private static String ipBytestoString(ByteBuf header, int addressLen) {
276         StringBuilder sb = new StringBuilder();
277         if (addressLen == 4) {
278             sb.append(header.readByte() & 0xff);
279             sb.append('.');
280             sb.append(header.readByte() & 0xff);
281             sb.append('.');
282             sb.append(header.readByte() & 0xff);
283             sb.append('.');
284             sb.append(header.readByte() & 0xff);
285         } else {
286             sb.append(Integer.toHexString(header.readUnsignedShort()));
287             sb.append(':');
288             sb.append(Integer.toHexString(header.readUnsignedShort()));
289             sb.append(':');
290             sb.append(Integer.toHexString(header.readUnsignedShort()));
291             sb.append(':');
292             sb.append(Integer.toHexString(header.readUnsignedShort()));
293             sb.append(':');
294             sb.append(Integer.toHexString(header.readUnsignedShort()));
295             sb.append(':');
296             sb.append(Integer.toHexString(header.readUnsignedShort()));
297             sb.append(':');
298             sb.append(Integer.toHexString(header.readUnsignedShort()));
299             sb.append(':');
300             sb.append(Integer.toHexString(header.readUnsignedShort()));
301         }
302         return sb.toString();
303     }
304 
305     /**
306      * Convert port to integer
307      *
308      * @param value                      the port
309      * @return                           port as an integer
310      * @throws HAProxyProtocolException  if port is not a valid integer
311      */
312     private static int portStringToInt(String value) {
313         int port;
314         try {
315             port = Integer.parseInt(value);
316         } catch (NumberFormatException e) {
317             throw new HAProxyProtocolException("invalid port: " + value, e);
318         }
319 
320         if (port <= 0 || port > 65535) {
321             throw new HAProxyProtocolException("invalid port: " + value + " (expected: 1 ~ 65535)");
322         }
323 
324         return port;
325     }
326 
327     /**
328      * Validate an address (IPv4, IPv6, Unix Socket)
329      *
330      * @param address                    human-readable address
331      * @param addrFamily                 the {@link AddressFamily} to check the address against
332      * @throws HAProxyProtocolException  if the address is invalid
333      */
334     private static void checkAddress(String address, AddressFamily addrFamily) {
335         if (addrFamily == null) {
336             throw new NullPointerException("addrFamily");
337         }
338 
339         switch (addrFamily) {
340             case AF_UNSPEC:
341                 if (address != null) {
342                     throw new HAProxyProtocolException("unable to validate an AF_UNSPEC address: " + address);
343                 }
344                 return;
345             case AF_UNIX:
346                 return;
347         }
348 
349         if (address == null) {
350             throw new NullPointerException("address");
351         }
352 
353         switch (addrFamily) {
354             case AF_IPv4:
355                 if (!NetUtil.isValidIpV4Address(address)) {
356                     throw new HAProxyProtocolException("invalid IPv4 address: " + address);
357                 }
358                 break;
359             case AF_IPv6:
360                 if (!NetUtil.isValidIpV6Address(address)) {
361                     throw new HAProxyProtocolException("invalid IPv6 address: " + address);
362                 }
363                 break;
364             default:
365                 throw new Error();
366         }
367     }
368 
369     /**
370      * Validate a UDP/TCP port
371      *
372      * @param port                       the UDP/TCP port
373      * @throws HAProxyProtocolException  if the port is out of range (0-65535 inclusive)
374      */
375     private static void checkPort(int port) {
376         if (port < 0 || port > 65535) {
377             throw new HAProxyProtocolException("invalid port: " + port + " (expected: 1 ~ 65535)");
378         }
379     }
380 
381     /**
382      * Returns the {@link HAProxyProtocolVersion} of this {@link HAProxyMessage}.
383      */
384     public HAProxyProtocolVersion protocolVersion() {
385         return protocolVersion;
386     }
387 
388     /**
389      * Returns the {@link HAProxyCommand} of this {@link HAProxyMessage}.
390      */
391     public HAProxyCommand command() {
392         return command;
393     }
394 
395     /**
396      * Returns the {@link HAProxyProxiedProtocol} of this {@link HAProxyMessage}.
397      */
398     public HAProxyProxiedProtocol proxiedProtocol() {
399         return proxiedProtocol;
400     }
401 
402     /**
403      * Returns the human-readable source address of this {@link HAProxyMessage}.
404      */
405     public String sourceAddress() {
406         return sourceAddress;
407     }
408 
409     /**
410      * Returns the human-readable destination address of this {@link HAProxyMessage}.
411      */
412     public String destinationAddress() {
413         return destinationAddress;
414     }
415 
416     /**
417      * Returns the UDP/TCP source port of this {@link HAProxyMessage}.
418      */
419     public int sourcePort() {
420         return sourcePort;
421     }
422 
423     /**
424      * Returns the UDP/TCP destination port of this {@link HAProxyMessage}.
425      */
426     public int destinationPort() {
427         return destinationPort;
428     }
429 }