View Javadoc
1   /*
2    * Copyright 2020 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.channel.ChannelHandler.Sharable;
20  import io.netty.channel.ChannelHandlerContext;
21  import io.netty.handler.codec.MessageToByteEncoder;
22  import io.netty.util.CharsetUtil;
23  import io.netty.util.NetUtil;
24  
25  import java.util.List;
26  
27  import static io.netty.handler.codec.haproxy.HAProxyConstants.*;
28  
29  /**
30   * Encodes an HAProxy proxy protocol message
31   *
32   * @see <a href="https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt">Proxy Protocol Specification</a>
33   */
34  @Sharable
35  public final class HAProxyMessageEncoder extends MessageToByteEncoder<HAProxyMessage> {
36  
37      private static final int V2_VERSION_BITMASK = 0x02 << 4;
38  
39      // Length for source/destination addresses for the UNIX family must be 108 bytes each.
40      static final int UNIX_ADDRESS_BYTES_LENGTH = 108;
41      static final int TOTAL_UNIX_ADDRESS_BYTES_LENGTH = UNIX_ADDRESS_BYTES_LENGTH * 2;
42  
43      public static final HAProxyMessageEncoder INSTANCE = new HAProxyMessageEncoder();
44  
45      private HAProxyMessageEncoder() {
46          super(HAProxyMessage.class);
47      }
48  
49      @Override
50      protected void encode(ChannelHandlerContext ctx, HAProxyMessage msg, ByteBuf out) throws Exception {
51          switch (msg.protocolVersion()) {
52              case V1:
53                  encodeV1(msg, out);
54                  break;
55              case V2:
56                  encodeV2(msg, out);
57                  break;
58              default:
59                  throw new HAProxyProtocolException("Unsupported version: " + msg.protocolVersion());
60          }
61      }
62  
63      private static void encodeV1(HAProxyMessage msg, ByteBuf out) {
64          out.writeBytes(TEXT_PREFIX);
65          out.writeByte((byte) ' ');
66          out.writeCharSequence(msg.proxiedProtocol().name(), CharsetUtil.US_ASCII);
67          out.writeByte((byte) ' ');
68          out.writeCharSequence(msg.sourceAddress(), CharsetUtil.US_ASCII);
69          out.writeByte((byte) ' ');
70          out.writeCharSequence(msg.destinationAddress(), CharsetUtil.US_ASCII);
71          out.writeByte((byte) ' ');
72          out.writeCharSequence(String.valueOf(msg.sourcePort()), CharsetUtil.US_ASCII);
73          out.writeByte((byte) ' ');
74          out.writeCharSequence(String.valueOf(msg.destinationPort()), CharsetUtil.US_ASCII);
75          out.writeByte((byte) '\r');
76          out.writeByte((byte) '\n');
77      }
78  
79      private static void encodeV2(HAProxyMessage msg, ByteBuf out) {
80          out.writeBytes(BINARY_PREFIX);
81          out.writeByte(V2_VERSION_BITMASK | msg.command().byteValue());
82          out.writeByte(msg.proxiedProtocol().byteValue());
83  
84          switch (msg.proxiedProtocol().addressFamily()) {
85              case AF_IPv4:
86              case AF_IPv6:
87                  byte[] srcAddrBytes = NetUtil.createByteArrayFromIpAddressString(msg.sourceAddress());
88                  byte[] dstAddrBytes = NetUtil.createByteArrayFromIpAddressString(msg.destinationAddress());
89                  // srcAddrLen + dstAddrLen + 4 (srcPort + dstPort) + numTlvBytes
90                  out.writeShort(srcAddrBytes.length + dstAddrBytes.length + 4 + msg.tlvNumBytes());
91                  out.writeBytes(srcAddrBytes);
92                  out.writeBytes(dstAddrBytes);
93                  out.writeShort(msg.sourcePort());
94                  out.writeShort(msg.destinationPort());
95                  encodeTlvs(msg.tlvs(), out);
96                  break;
97              case AF_UNIX:
98                  out.writeShort(TOTAL_UNIX_ADDRESS_BYTES_LENGTH + msg.tlvNumBytes());
99                  int srcAddrBytesWritten = out.writeCharSequence(msg.sourceAddress(), CharsetUtil.US_ASCII);
100                 out.writeZero(UNIX_ADDRESS_BYTES_LENGTH - srcAddrBytesWritten);
101                 int dstAddrBytesWritten = out.writeCharSequence(msg.destinationAddress(), CharsetUtil.US_ASCII);
102                 out.writeZero(UNIX_ADDRESS_BYTES_LENGTH - dstAddrBytesWritten);
103                 encodeTlvs(msg.tlvs(), out);
104                 break;
105             case AF_UNSPEC:
106                 out.writeShort(0);
107                 break;
108             default:
109                 throw new HAProxyProtocolException("unexpected addrFamily");
110         }
111     }
112 
113     private static void encodeTlv(HAProxyTLV haProxyTLV, ByteBuf out) {
114         if (haProxyTLV instanceof HAProxySSLTLV) {
115             HAProxySSLTLV ssltlv = (HAProxySSLTLV) haProxyTLV;
116             out.writeByte(haProxyTLV.typeByteValue());
117             out.writeShort(ssltlv.contentNumBytes());
118             out.writeByte(ssltlv.client());
119             out.writeInt(ssltlv.verify());
120             encodeChildTlvs(ssltlv.encapsulatedTLVs(), out);
121         } else {
122             out.writeByte(haProxyTLV.typeByteValue());
123             ByteBuf value = haProxyTLV.content();
124             int readableBytes = value.readableBytes();
125             out.writeShort(readableBytes);
126             // Do not use readSlice(...) (or similar) here as it would mutate the reader index of the
127             // content, which is observable by the caller (e.g. HAProxyMessage.tlvs() exposes the same
128             // TLV instance both as a top-level entry and, if it is a direct child of a PP2_TYPE_SSL TLV,
129             // flattened into the top-level list) and could result in the same TLV being encoded
130             // correctly the first time and as empty content thereafter.
131             out.writeBytes(value, value.readerIndex(), readableBytes);
132         }
133     }
134 
135     /**
136      * Encodes a genuine tree of encapsulated TLVs, e.g. the direct children of a {@link HAProxySSLTLV}.
137      * Unlike {@link HAProxyMessage#tlvs()}, this list is not a flattened view, so every element must be
138      * encoded.
139      */
140     private static void encodeChildTlvs(List<HAProxyTLV> haProxyTLVs, ByteBuf out) {
141         for (int i = 0; i < haProxyTLVs.size(); i++) {
142             encodeTlv(haProxyTLVs.get(i), out);
143         }
144     }
145 
146     /**
147      * Encodes the top-level TLV list as returned by {@link HAProxyMessage#tlvs()}. That list flattens the
148      * direct children of any {@link HAProxySSLTLV} into it for convenience, so those flattened entries must
149      * be skipped here: they are already encoded (recursively) as part of their parent
150      * {@link HAProxySSLTLV}. Encoding them again would both duplicate their bytes on the wire and make the
151      * declared TLV length (see {@link HAProxyMessage#tlvNumBytes()}) not match the number of bytes actually
152      * written.
153      */
154     private static void encodeTlvs(List<HAProxyTLV> haProxyTLVs, ByteBuf out) {
155         for (int i = 0; i < haProxyTLVs.size(); i += 1 + HAProxyMessage.flattenedChildren(haProxyTLVs, i)) {
156             encodeTlv(haProxyTLVs.get(i), out);
157         }
158     }
159 }