Skip to content
This repository has been archived by the owner on Oct 30, 2023. It is now read-only.

TRACE header support #72

Closed
wants to merge 7 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ job-defaults: &job-defaults
test_results_directory: &test_results_directory
/tmp/test-results
environment:
- RELEASE_BRANCH: vgs-edition
- RELEASE_BRANCH: trace_header_support
- AWS_DEFAULT_REGION: us-west-2
- AWS_REGION: us-west-2
- AWS_PROFILE: vgs-dev
Expand Down Expand Up @@ -86,7 +86,7 @@ jobs:
- <<: *setup-env
- add_ssh_keys:
fingerprints:
- "5c:bb:63:02:38:b1:b5:ff:a8:6e:0f:fd:d3:20:d9:1c"
- "b9:76:24:a1:6e:4c:fb:04:b3:0c:05:d9:b2:22:c1:a4"
- run:
name: Release
command: |
Expand Down
6 changes: 3 additions & 3 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<groupId>org.littleshoot</groupId>
<artifactId>littleproxy</artifactId>
<packaging>jar</packaging>
<version>1.1.15-VGS-SNAPSHOT</version>
<version>1.2.0-VGS-SNAPSHOT</version>
<name>LittleProxy</name>
<description>
LittleProxy is a high performance HTTP proxy written in Java and using the Netty networking framework.
Expand Down Expand Up @@ -606,8 +606,8 @@
<skipTestProject>true</skipTestProject>
<allowSnapshots>true</allowSnapshots>
<gitFlowConfig>
<productionBranch>vgs-edition</productionBranch>
<developmentBranch>vgs-edition</developmentBranch>
<productionBranch>trace_header_support</productionBranch>
<developmentBranch>trace_header_support</developmentBranch>
<releaseBranchPrefix>release-</releaseBranchPrefix>
<versionTagPrefix></versionTagPrefix>
</gitFlowConfig>
Expand Down
2 changes: 1 addition & 1 deletion scripts/env.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,6 @@ aws_access_key_id=$AWS_ACCESS_KEY_ID
aws_secret_access_key=$AWS_SECRET_ACCESS_KEY
[vgs-dev]
region = us-west-2
role_arn = arn:aws:iam::883127560329:role/StageDeploy
role_arn = arn:aws:iam::883127560329:role/VGSStageDeploy
source_profile = default
" > ~/.aws/credentials
Original file line number Diff line number Diff line change
Expand Up @@ -803,7 +803,7 @@ private void initChannelPipeline(ChannelPipeline pipeline) {
pipeline.addLast("bytesReadMonitor", bytesReadMonitor);
pipeline.addLast("bytesWrittenMonitor", bytesWrittenMonitor);

pipeline.addLast("proxyProtocolReader", new HttpProxyProtocolRequestDecoder());
pipeline.addLast("proxyProtocolReader", new ProtocolHeadersRequestDecoder());

pipeline.addLast("encoder", new HttpResponseEncoder());
// We want to allow longer request lines, headers, and chunks
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
package org.littleshoot.proxy.impl;

import java.util.regex.Matcher;
import java.util.regex.Pattern;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.AttributeKey;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class HttpProxyProtocolRequestDecoder extends ChannelInboundHandlerAdapter {
public class ProtocolHeadersRequestDecoder extends ChannelInboundHandlerAdapter {

public static final AttributeKey<String> SOURCE_IP_ATTRIBUTE = AttributeKey.valueOf("sourceIp");
public static final AttributeKey<String> PROTOCOL_TRACE_ID_ATTRIBUTE = AttributeKey.valueOf("protocolTraceId");

// Pattern:
// PROXY_STRING + single space + INET_PROTOCOL + single space + CLIENT_IP + single space + PROXY_IP + single space + CLIENT_PORT + single space + PROXY_PORT + "\r\n"
Expand All @@ -21,10 +21,19 @@ public class HttpProxyProtocolRequestDecoder extends ChannelInboundHandlerAdapte
// Source:
// https://docs.aws.amazon.com/elasticloadbalancing/latest/classic/enable-proxy-protocol.html
private static final Pattern TCP4_PROXY_PROTOCOL_HEADER_PATTERN =
Pattern.compile("PROXY TCP4 (((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\\.)?){4}) ((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\\.)?){4} \\d+ \\d+\\r\\n");
Pattern.compile("^PROXY TCP4 (((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\\.)?){4}) ((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(\\.)?){4} \\d+ \\d+\\r\\n");

private static final Pattern TCP6_PROXY_PROTOCOL_HEADER_PATTERN =
Pattern.compile("PROXY TCP6 (([0-9a-f]{1,4}:){7}([0-9a-f]){1,4}) ([0-9a-f]{1,4}:){7}([0-9a-f]){1,4} \\d+ \\d+\\r\\n", Pattern.CASE_INSENSITIVE);
Pattern.compile("^PROXY TCP6 (([0-9a-f]{1,4}:){7}([0-9a-f]){1,4}) ([0-9a-f]{1,4}:){7}([0-9a-f]){1,4} \\d+ \\d+\\r\\n", Pattern.CASE_INSENSITIVE);

// Pattern:
// TRACE <32HEX>\r\n
// Example:
// TRACE 01234567890abcdef01234567890abcdef\r\n
// Source:
// https://github.com/verygoodsecurity/nginx/pull/1
// https://github.com/opentracing/specification/issues/150
private static final Pattern TRACE_HEADER_PATTERN = Pattern.compile("^TRACE ([0-9a-f]{32})\\r\\n");

@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
Expand All @@ -51,6 +60,20 @@ public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception
int proxyProtocolHeaderIndex = body.indexOf("\r\n");
String stripped = body.substring(proxyProtocolHeaderIndex + 2); // +2 for \r\n

String traceId;

Matcher traceHeaderMatcher = TRACE_HEADER_PATTERN.matcher(stripped);
if (traceHeaderMatcher.find()) {
traceId = traceHeaderMatcher.group(1);
} else {
buf.clear().writeBytes(stripped.getBytes());
ctx.fireChannelRead(buf);
return;
}

ctx.channel().attr(PROTOCOL_TRACE_ID_ATTRIBUTE).set(traceId);

stripped = stripped.substring(6 + 32 + 2); // TRACE 32HEX\r\n
buf.clear().writeBytes(stripped.getBytes());

ctx.fireChannelRead(buf);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,25 @@
package org.littleshoot.proxy;

import static org.hamcrest.CoreMatchers.nullValue;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.littleshoot.proxy.impl.ProtocolHeadersRequestDecoder.SOURCE_IP_ATTRIBUTE;
import static org.littleshoot.proxy.impl.ProtocolHeadersRequestDecoder.PROTOCOL_TRACE_ID_ATTRIBUTE;

import com.tngtech.java.junit.dataprovider.DataProvider;
import com.tngtech.java.junit.dataprovider.DataProviderRunner;
import com.tngtech.java.junit.dataprovider.UseDataProvider;

import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.HttpObject;
import io.netty.handler.codec.http.HttpRequest;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.concurrent.atomic.AtomicReference;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.io.IOUtils;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Server;
Expand All @@ -16,27 +32,8 @@
import org.littleshoot.proxy.impl.DefaultHttpProxyServer;
import org.littleshoot.proxy.impl.ThreadPoolConfiguration;

import java.io.IOException;
import java.io.InputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.concurrent.atomic.AtomicReference;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.http.HttpObject;
import io.netty.handler.codec.http.HttpRequest;

import static org.hamcrest.CoreMatchers.nullValue;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.littleshoot.proxy.impl.HttpProxyProtocolRequestDecoder.SOURCE_IP_ATTRIBUTE;

@RunWith(DataProviderRunner.class)
public class ProxyProtocolRequestDecoderTest {
public class ProtocolHeadersRequestDecoderTest {

private Server webServer;
private HttpProxyServer proxyServer;
Expand Down Expand Up @@ -87,7 +84,7 @@ public static Object[][] validCases() {
return new Object[][]{
{"", nullValue()}, // No Proxy Header, typical case
{"PROXY TCP4 11.22.33.44 99.88.77.66 5555 6666\r\n", equalTo("11.22.33.44")}, // Valid TCP4
{"PROXY TCP6 FE80:0000:0000:0000:0202:B3FF:FE1E:8329 1200:0000:AB00:1234:0000:2552:7777:1313 5555 6666\r\n", equalTo("FE80:0000:0000:0000:0202:B3FF:FE1E:8329")}, // Valid TCP6
{"PROXY TCP6 FE80:0000:0000:0000:0202:B3FF:FE1E:8329 1200:0000:AB00:1234:0000:2552:7777:1313 5555 6666\r\n", equalTo("FE80:0000:0000:0000:0202:B3FF:FE1E:8329")} // Valid TCP6
};
}

Expand All @@ -98,6 +95,7 @@ public static Object[][] invalidCases() {
{"PROXY TCP6 FE80::0202:B3FF:FE1E:8329 1200::AB00:1234::2552:7777:1313 5555 6666\r\n"}, // Invalid collapsed TCP6, should be 39 characters
{"PROXY TCP4 555.22.33.44 99.88.77.66 5555 6666"}, // Missing \r\n
{"PROXY UNKNOWN 11.22.33.44 99.88.77.66 5555 6666\n"}, // We do not support UNKNOWN
{"testPROXY TCP4 555.22.33.44 99.88.77.66 5555 6666\r\n"}, // PROXY is not the first string in request
};
}

Expand All @@ -116,6 +114,44 @@ public void testInvalid(String proxyProtocolHeader) throws Exception {
assertThat(context, nullValue());
}

@DataProvider
public static Object[][] validCasesTraceHeader() {
return new Object[][]{
{"PROXY TCP4 11.22.33.44 99.88.77.66 5555 6666\r\nTRACE 0123456789abcdef0123456789abcdef\r\n", equalTo("0123456789abcdef0123456789abcdef")}, // PROXY_TCP4 + TRACE
{"PROXY TCP6 FE80:0000:0000:0000:0202:B3FF:FE1E:8329 1200:0000:AB00:1234:0000:2552:7777:1313 5555 6666\r\nTRACE 0123456789abcdef0123456789abcdef\r\n", equalTo("0123456789abcdef0123456789abcdef")} // PROXY_TCP6 + TRACE
};
}

@DataProvider
public static Object[][] invalidCasesTraceHeader() {
return new Object[][]{
{"TRACE 0123456789abcdef0123456789abcdef\r\n"}, // NO PROXY header in front of trace
{"PROXY TCP4 55.22.33.44 99.88.77.66 5555 6666\r\nTRACE 0123456789abcdef0123456789abcdef\n\n"}, // Invalid ending
{"PROXY TCP4 55.22.33.44 99.88.77.66 5555 6666\r\nTRACE 01234567890abcdef01234567890abcde\r\n"}, // Too short
{"PROXY TCP4 55.22.33.44 99.88.77.66 5555 6666\r\nTRACE 0123456789abcdef0123456789abcdeff\r\n"}, // Too long
{"PROXY TCP4 55.22.33.44 99.88.77.66 5555 6666\r\nTRACE 0123456789ABCDEF0123456789ABCDEF\r\n"}, // Uppercase characters
{"PROXY TCP4 55.22.33.44 99.88.77.66 5555 6666\r\nTRACE asdfasdfasdfasdfasdfasdfasdfasdfa\r\n"}, // Invalid characters
{"PROXY TCP4 555.22.33.44 99.88.77.66 5555 6666\r\nTRACE 0123456789abcdef0123456789abcdef\r\n"}, // Invalid IP in PROXY header in front
{"PROXY TCP4 55.22.33.44 99.88.77.66 5555 6666\rTRACE 0123456789abcdef0123456789abcdef\r\n"}, // Invalid delimiter in between PROXY and TRACE headers
{"PROXY TCP4 55.22.33.44 99.88.77.66 5555 6666\r\ntestTRACE 0123456789abcdef0123456789abcdef\r\n"}, // Invalid characters in front of TRACE header
};
}

@Test
@UseDataProvider("validCasesTraceHeader")
public void testValidTraceHeader(String traceHeader, Matcher traceMatcher) throws Exception {
ChannelHandlerContext context = runTest(traceHeader);

assertThat(context.channel().attr(PROTOCOL_TRACE_ID_ATTRIBUTE).get(), traceMatcher);
}

@Test
@UseDataProvider("invalidCasesTraceHeader")
public void testInvalidTraceHeader(String traceHeader) throws Exception {
ChannelHandlerContext context = runTest(traceHeader);
assertThat(context, nullValue());
}

private ChannelHandlerContext runTest(String proxyProtocolHeader) throws Exception {
final AtomicReference<ChannelHandlerContext> serverCtxReference = new AtomicReference<>();

Expand Down