Added PROXY Protocol TLV support
Motivation: The current PROXY protocol implementation does not have support for optional Type-Length-Value fields. This pull requests adds the TLV values as specified in the PROXY protocol specification (http://www.haproxy.org/download/1.5/doc/proxy-protocol.txt) and adds support for arbitrary TLVs. Modifications: The existing HAProxyMessage implements an additional TLV reading operation. A small bug in the AF_UNIX reader which didn’t set the reader index correctly was also fixed. Result: The PROXY protocol supports TLVs
This commit is contained in:
parent
b03b0f22d1
commit
1f0d47dee7
@ -21,6 +21,10 @@ import io.netty.util.ByteProcessor;
|
||||
import io.netty.util.CharsetUtil;
|
||||
import io.netty.util.NetUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Message container for decoded HAProxy proxy protocol parameters
|
||||
*/
|
||||
@ -54,6 +58,7 @@ public final class HAProxyMessage {
|
||||
private final String destinationAddress;
|
||||
private final int sourcePort;
|
||||
private final int destinationPort;
|
||||
private final List<HAProxyTLV> tlvs;
|
||||
|
||||
/**
|
||||
* Creates a new instance
|
||||
@ -73,6 +78,18 @@ public final class HAProxyMessage {
|
||||
HAProxyProtocolVersion protocolVersion, HAProxyCommand command, HAProxyProxiedProtocol proxiedProtocol,
|
||||
String sourceAddress, String destinationAddress, int sourcePort, int destinationPort) {
|
||||
|
||||
this(protocolVersion, command, proxiedProtocol,
|
||||
sourceAddress, destinationAddress, sourcePort, destinationPort, Collections.<HAProxyTLV>emptyList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new instance
|
||||
*/
|
||||
private HAProxyMessage(
|
||||
HAProxyProtocolVersion protocolVersion, HAProxyCommand command, HAProxyProxiedProtocol proxiedProtocol,
|
||||
String sourceAddress, String destinationAddress, int sourcePort, int destinationPort,
|
||||
List<HAProxyTLV> tlvs) {
|
||||
|
||||
if (proxiedProtocol == null) {
|
||||
throw new NullPointerException("proxiedProtocol");
|
||||
}
|
||||
@ -90,6 +107,7 @@ public final class HAProxyMessage {
|
||||
this.destinationAddress = destinationAddress;
|
||||
this.sourcePort = sourcePort;
|
||||
this.destinationPort = destinationPort;
|
||||
this.tlvs = Collections.unmodifiableList(tlvs);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -214,7 +232,71 @@ public final class HAProxyMessage {
|
||||
dstPort = header.readUnsignedShort();
|
||||
}
|
||||
|
||||
return new HAProxyMessage(ver, cmd, protAndFam, srcAddress, dstAddress, srcPort, dstPort);
|
||||
final List<HAProxyTLV> tlvs = readTlvs(header);
|
||||
|
||||
return new HAProxyMessage(ver, cmd, protAndFam, srcAddress, dstAddress, srcPort, dstPort, tlvs);
|
||||
}
|
||||
|
||||
private static List<HAProxyTLV> readTlvs(final ByteBuf header) {
|
||||
HAProxyTLV haProxyTLV = readNextTLV(header);
|
||||
if (haProxyTLV == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
// In most cases there are less than 4 TLVs available
|
||||
List<HAProxyTLV> haProxyTLVs = new ArrayList<HAProxyTLV>(4);
|
||||
|
||||
do {
|
||||
haProxyTLVs.add(haProxyTLV);
|
||||
if (haProxyTLV instanceof HAProxySSLTLV) {
|
||||
haProxyTLVs.addAll(((HAProxySSLTLV) haProxyTLV).encapsulatedTLVs());
|
||||
}
|
||||
} while ((haProxyTLV = readNextTLV(header)) != null);
|
||||
return haProxyTLVs;
|
||||
}
|
||||
|
||||
private static HAProxyTLV readNextTLV(final ByteBuf header) {
|
||||
|
||||
// We need at least 4 bytes for a TLV
|
||||
if (header.readableBytes() < 4) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final byte typeAsByte = header.readByte();
|
||||
final HAProxyTLV.Type type = HAProxyTLV.Type.typeForByteValue(typeAsByte);
|
||||
|
||||
final int length = header.readUnsignedShort();
|
||||
switch (type) {
|
||||
case PP2_TYPE_SSL:
|
||||
final ByteBuf rawContent = header.retainedSlice(header.readerIndex(), length);
|
||||
final ByteBuf byteBuf = header.readSlice(length);
|
||||
final byte client = byteBuf.readByte();
|
||||
final int verify = byteBuf.readInt();
|
||||
|
||||
if (byteBuf.readableBytes() >= 4) {
|
||||
|
||||
final List<HAProxyTLV> encapsulatedTlvs = new ArrayList<HAProxyTLV>(4);
|
||||
do {
|
||||
final HAProxyTLV haProxyTLV = readNextTLV(byteBuf);
|
||||
if (haProxyTLV == null) {
|
||||
break;
|
||||
}
|
||||
encapsulatedTlvs.add(haProxyTLV);
|
||||
} while (byteBuf.readableBytes() >= 4);
|
||||
|
||||
return new HAProxySSLTLV(verify, client, encapsulatedTlvs, rawContent);
|
||||
}
|
||||
return new HAProxySSLTLV(verify, client, Collections.<HAProxyTLV>emptyList(), rawContent);
|
||||
// If we're not dealing with a SSL Type, we can use the same mechanism
|
||||
case PP2_TYPE_ALPN:
|
||||
case PP2_TYPE_AUTHORITY:
|
||||
case PP2_TYPE_SSL_VERSION:
|
||||
case PP2_TYPE_SSL_CN:
|
||||
case PP2_TYPE_NETNS:
|
||||
case OTHER:
|
||||
return new HAProxyTLV(type, typeAsByte, header.readRetainedSlice(length));
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -428,4 +510,13 @@ public final class HAProxyMessage {
|
||||
public int destinationPort() {
|
||||
return destinationPort;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of {@link HAProxyTLV} or an empty list if no TLVs are present.
|
||||
* <p>
|
||||
* TLVs are only available for the Proxy Protocol V2
|
||||
*/
|
||||
public List<HAProxyTLV> tlvs() {
|
||||
return tlvs;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright 2016 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package io.netty.handler.codec.haproxy;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Represents a {@link HAProxyTLV} of the type {@link HAProxyTLV.Type#PP2_TYPE_SSL}.
|
||||
* This TLV encapsulates other TLVs and has additional information like verification information and a client bitfield.
|
||||
*/
|
||||
public final class HAProxySSLTLV extends HAProxyTLV {
|
||||
|
||||
private final int verify;
|
||||
private final List<HAProxyTLV> tlvs;
|
||||
private final byte clientBitField;
|
||||
|
||||
/**
|
||||
* Creates a new HAProxySSLTLV
|
||||
*
|
||||
* @param verify the verification result as defined in the specification for the pp2_tlv_ssl struct (see
|
||||
* http://www.haproxy.org/download/1.5/doc/proxy-protocol.txt)
|
||||
* @param clientBitField the bitfield with client information
|
||||
* @param tlvs the encapsulated {@link HAProxyTLV}s
|
||||
* @param rawContent the raw TLV content
|
||||
*/
|
||||
HAProxySSLTLV(final int verify, final byte clientBitField, final List<HAProxyTLV> tlvs, final ByteBuf rawContent) {
|
||||
super(Type.PP2_TYPE_SSL, (byte) 0x20, rawContent);
|
||||
|
||||
this.verify = verify;
|
||||
this.tlvs = Collections.unmodifiableList(tlvs);
|
||||
this.clientBitField = clientBitField;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if the bit field for PP2_CLIENT_CERT_CONN was set
|
||||
*/
|
||||
public boolean isPP2ClientCertConn() {
|
||||
return (clientBitField & 0x2) != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if the bit field for PP2_CLIENT_SSL was set
|
||||
*/
|
||||
public boolean isPP2ClientSSL() {
|
||||
return (clientBitField & 0x1) != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if the bit field for PP2_CLIENT_CERT_SESS was set
|
||||
*/
|
||||
public boolean isPP2ClientCertSess() {
|
||||
return (clientBitField & 0x4) != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the verification result
|
||||
*/
|
||||
public int verify() {
|
||||
return verify;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an unmodifiable Set of encapsulated {@link HAProxyTLV}s.
|
||||
*/
|
||||
public List<HAProxyTLV> encapsulatedTLVs() {
|
||||
return tlvs;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,151 @@
|
||||
/*
|
||||
* Copyright 2016 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package io.netty.handler.codec.haproxy;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.DefaultByteBufHolder;
|
||||
|
||||
import static io.netty.util.internal.ObjectUtil.*;
|
||||
|
||||
/**
|
||||
* A Type-Length Value (TLV vector) that can be added to the PROXY protocol
|
||||
* to include additional information like SSL information.
|
||||
*
|
||||
* @see HAProxySSLTLV
|
||||
*/
|
||||
public class HAProxyTLV extends DefaultByteBufHolder {
|
||||
|
||||
private final Type type;
|
||||
private final byte typeByteValue;
|
||||
|
||||
/**
|
||||
* The registered types a TLV can have regarding the PROXY protocol 1.5 spec
|
||||
*/
|
||||
public enum Type {
|
||||
PP2_TYPE_ALPN,
|
||||
PP2_TYPE_AUTHORITY,
|
||||
PP2_TYPE_SSL,
|
||||
PP2_TYPE_SSL_VERSION,
|
||||
PP2_TYPE_SSL_CN,
|
||||
PP2_TYPE_NETNS,
|
||||
/**
|
||||
* A TLV type that is not officially defined in the spec. May be used for nonstandard TLVs
|
||||
*/
|
||||
OTHER;
|
||||
|
||||
/**
|
||||
* Returns the the {@link Type} for a specific byte value as defined in the PROXY protocol 1.5 spec
|
||||
* <p>
|
||||
* If the byte value is not an official one, it will return {@link Type#OTHER}.
|
||||
*
|
||||
* @param byteValue the byte for a type
|
||||
*
|
||||
* @return the {@link Type} of a TLV
|
||||
*/
|
||||
public static Type typeForByteValue(final byte byteValue) {
|
||||
switch (byteValue) {
|
||||
case 0x01:
|
||||
return PP2_TYPE_ALPN;
|
||||
case 0x02:
|
||||
return PP2_TYPE_AUTHORITY;
|
||||
case 0x20:
|
||||
return PP2_TYPE_SSL;
|
||||
case 0x21:
|
||||
return PP2_TYPE_SSL_VERSION;
|
||||
case 0x22:
|
||||
return PP2_TYPE_SSL_CN;
|
||||
case 0x30:
|
||||
return PP2_TYPE_NETNS;
|
||||
default:
|
||||
return OTHER;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new HAProxyTLV
|
||||
*
|
||||
* @param type the {@link Type} of the TLV
|
||||
* @param typeByteValue the byteValue of the TLV. This is especially important if non-standard TLVs are used
|
||||
* @param content the raw content of the TLV
|
||||
*/
|
||||
HAProxyTLV(final Type type, final byte typeByteValue, final ByteBuf content) {
|
||||
super(content);
|
||||
checkNotNull(type, "type");
|
||||
|
||||
this.type = type;
|
||||
this.typeByteValue = typeByteValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link Type} of this TLV
|
||||
*/
|
||||
public Type type() {
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the type of the TLV as byte
|
||||
*/
|
||||
public byte typeByteValue() {
|
||||
return typeByteValue;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HAProxyTLV copy() {
|
||||
return replace(content().copy());
|
||||
}
|
||||
|
||||
@Override
|
||||
public HAProxyTLV duplicate() {
|
||||
return replace(content().duplicate());
|
||||
}
|
||||
|
||||
@Override
|
||||
public HAProxyTLV retainedDuplicate() {
|
||||
return replace(content().retainedDuplicate());
|
||||
}
|
||||
|
||||
@Override
|
||||
public HAProxyTLV replace(ByteBuf content) {
|
||||
return new HAProxyTLV(type, typeByteValue, content);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HAProxyTLV retain() {
|
||||
super.retain();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HAProxyTLV retain(int increment) {
|
||||
super.retain(increment);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HAProxyTLV touch() {
|
||||
super.touch();
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HAProxyTLV touch(Object hint) {
|
||||
super.touch(hint);
|
||||
return this;
|
||||
}
|
||||
}
|
@ -26,6 +26,8 @@ import io.netty.util.CharsetUtil;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static io.netty.buffer.Unpooled.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
@ -586,6 +588,71 @@ public class HAProxyMessageDecoderTest {
|
||||
assertFalse(ch.finish());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testV2WithSslTLVs() throws Exception {
|
||||
ch = new EmbeddedChannel(new HAProxyMessageDecoder());
|
||||
|
||||
final byte[] bytes = {
|
||||
13, 10, 13, 10, 0, 13, 10, 81, 85, 73, 84, 10, 33, 17, 0, 35, 127, 0, 0, 1, 127, 0, 0, 1,
|
||||
-55, -90, 7, 89, 32, 0, 20, 5, 0, 0, 0, 0, 33, 0, 5, 84, 76, 83, 118, 49, 34, 0, 4, 76, 69, 65, 70
|
||||
};
|
||||
|
||||
int startChannels = ch.pipeline().names().size();
|
||||
assertTrue(ch.writeInbound(copiedBuffer(bytes)));
|
||||
Object msgObj = ch.readInbound();
|
||||
assertEquals(startChannels - 1, ch.pipeline().names().size());
|
||||
HAProxyMessage msg = (HAProxyMessage) msgObj;
|
||||
|
||||
assertEquals(HAProxyProtocolVersion.V2, msg.protocolVersion());
|
||||
assertEquals(HAProxyCommand.PROXY, msg.command());
|
||||
assertEquals(HAProxyProxiedProtocol.TCP4, msg.proxiedProtocol());
|
||||
assertEquals("127.0.0.1", msg.sourceAddress());
|
||||
assertEquals("127.0.0.1", msg.destinationAddress());
|
||||
assertEquals(51622, msg.sourcePort());
|
||||
assertEquals(1881, msg.destinationPort());
|
||||
final List<HAProxyTLV> tlvs = msg.tlvs();
|
||||
|
||||
assertEquals(3, tlvs.size());
|
||||
final HAProxyTLV firstTlv = tlvs.get(0);
|
||||
assertEquals(HAProxyTLV.Type.PP2_TYPE_SSL, firstTlv.type());
|
||||
final HAProxySSLTLV sslTlv = (HAProxySSLTLV) firstTlv;
|
||||
assertEquals(0, sslTlv.verify());
|
||||
assertTrue(sslTlv.isPP2ClientSSL());
|
||||
assertTrue(sslTlv.isPP2ClientCertSess());
|
||||
assertFalse(sslTlv.isPP2ClientCertConn());
|
||||
|
||||
final HAProxyTLV secondTlv = tlvs.get(1);
|
||||
|
||||
assertEquals(HAProxyTLV.Type.PP2_TYPE_SSL_VERSION, secondTlv.type());
|
||||
ByteBuf secondContentBuf = secondTlv.content();
|
||||
byte[] secondContent = new byte[secondContentBuf.readableBytes()];
|
||||
secondContentBuf.readBytes(secondContent);
|
||||
assertArrayEquals("TLSv1".getBytes(CharsetUtil.US_ASCII), secondContent);
|
||||
|
||||
final HAProxyTLV thirdTLV = tlvs.get(2);
|
||||
assertEquals(HAProxyTLV.Type.PP2_TYPE_SSL_CN, thirdTLV.type());
|
||||
ByteBuf thirdContentBuf = thirdTLV.content();
|
||||
byte[] thirdContent = new byte[thirdContentBuf.readableBytes()];
|
||||
thirdContentBuf.readBytes(thirdContent);
|
||||
assertArrayEquals("LEAF".getBytes(CharsetUtil.US_ASCII), thirdContent);
|
||||
|
||||
assertTrue(sslTlv.encapsulatedTLVs().contains(secondTlv));
|
||||
assertTrue(sslTlv.encapsulatedTLVs().contains(thirdTLV));
|
||||
|
||||
assertTrue(0 < firstTlv.refCnt());
|
||||
assertTrue(0 < secondTlv.refCnt());
|
||||
assertTrue(0 < thirdTLV.refCnt());
|
||||
assertFalse(thirdTLV.release());
|
||||
assertFalse(secondTlv.release());
|
||||
assertTrue(firstTlv.release());
|
||||
assertEquals(0, firstTlv.refCnt());
|
||||
assertEquals(0, secondTlv.refCnt());
|
||||
assertEquals(0, thirdTLV.refCnt());
|
||||
|
||||
assertNull(ch.readInbound());
|
||||
assertFalse(ch.finish());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testV2WithTLV() {
|
||||
ch = new EmbeddedChannel(new HAProxyMessageDecoder(4));
|
||||
|
@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright 2016 The Netty Project
|
||||
*
|
||||
* The Netty Project licenses this file to you under the Apache License,
|
||||
* version 2.0 (the "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at:
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package io.netty.handler.codec.haproxy;
|
||||
|
||||
import io.netty.buffer.Unpooled;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class HAProxySSLTLVTest {
|
||||
|
||||
@Test
|
||||
public void testClientBitmask() throws Exception {
|
||||
|
||||
// 0b0000_0111
|
||||
final byte allClientsEnabled = 0x7;
|
||||
final HAProxySSLTLV allClientsEnabledTLV =
|
||||
new HAProxySSLTLV(0, allClientsEnabled, Collections.<HAProxyTLV>emptyList(), Unpooled.buffer());
|
||||
|
||||
assertTrue(allClientsEnabledTLV.isPP2ClientCertConn());
|
||||
assertTrue(allClientsEnabledTLV.isPP2ClientSSL());
|
||||
assertTrue(allClientsEnabledTLV.isPP2ClientCertSess());
|
||||
|
||||
assertTrue(allClientsEnabledTLV.release());
|
||||
|
||||
// 0b0000_0101
|
||||
final byte clientSSLandClientCertSessEnabled = 0x5;
|
||||
|
||||
final HAProxySSLTLV clientSSLandClientCertSessTLV =
|
||||
new HAProxySSLTLV(0, clientSSLandClientCertSessEnabled, Collections.<HAProxyTLV>emptyList(),
|
||||
Unpooled.buffer());
|
||||
|
||||
assertFalse(clientSSLandClientCertSessTLV.isPP2ClientCertConn());
|
||||
assertTrue(clientSSLandClientCertSessTLV.isPP2ClientSSL());
|
||||
assertTrue(clientSSLandClientCertSessTLV.isPP2ClientCertSess());
|
||||
|
||||
assertTrue(clientSSLandClientCertSessTLV.release());
|
||||
// 0b0000_0000
|
||||
final byte noClientEnabled = 0x0;
|
||||
|
||||
final HAProxySSLTLV noClientTlv =
|
||||
new HAProxySSLTLV(0, noClientEnabled, Collections.<HAProxyTLV>emptyList(),
|
||||
Unpooled.buffer());
|
||||
|
||||
assertFalse(noClientTlv.isPP2ClientCertConn());
|
||||
assertFalse(noClientTlv.isPP2ClientSSL());
|
||||
assertFalse(noClientTlv.isPP2ClientCertSess());
|
||||
|
||||
assertTrue(noClientTlv.release());
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user