Skip to content

Commit 8b465f8

Browse files
committed
Make X-Forwarded-For and X-Forwarded-Port available as request headers when using proxy protocol.
1 parent 183f66e commit 8b465f8

File tree

8 files changed

+48
-15
lines changed

8 files changed

+48
-15
lines changed

webserver/http2/src/main/java/io/helidon/webserver/http2/Http2Connection.java

+15
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@
6464
import io.helidon.webserver.http2.spi.Http2SubProtocolSelector;
6565
import io.helidon.webserver.spi.ServerConnection;
6666

67+
import static io.helidon.http.HeaderNames.X_FORWARDED_FOR;
68+
import static io.helidon.http.HeaderNames.X_FORWARDED_PORT;
6769
import static io.helidon.http.HeaderNames.X_HELIDON_CN;
6870
import static io.helidon.http.http2.Http2Util.PREFACE_LENGTH;
6971
import static java.lang.System.Logger.Level.DEBUG;
@@ -609,6 +611,19 @@ private void doHeaders(Semaphore requestSemaphore) {
609611
ctx.remotePeer().tlsCertificates()
610612
.flatMap(TlsUtils::parseCn)
611613
.ifPresent(cn -> connectionHeaders.add(X_HELIDON_CN, cn));
614+
615+
// proxy protocol related headers X-Forwarded-For and X-Forwarded-Port
616+
ctx.proxyProtocolData().ifPresent(proxyProtocolData -> {
617+
String sourceAddress = proxyProtocolData.sourceAddress();
618+
if (!sourceAddress.isEmpty()) {
619+
connectionHeaders.add(X_FORWARDED_FOR, sourceAddress);
620+
}
621+
int sourcePort = proxyProtocolData.sourcePort();
622+
if (sourcePort != -1) {
623+
connectionHeaders.set(X_FORWARDED_PORT, sourcePort);
624+
}
625+
});
626+
612627
initConnectionHeaders = false;
613628
}
614629

webserver/tests/webserver/src/test/java/io/helidon/webserver/tests/ProxyProtocolTest.java

+4-1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package io.helidon.webserver.tests;
1717

1818
import io.helidon.common.testing.http.junit5.SocketHttpClient;
19+
import io.helidon.http.HeaderNames;
1920
import io.helidon.http.Method;
2021
import io.helidon.http.Status;
2122
import io.helidon.webserver.ProxyProtocolData;
@@ -57,7 +58,9 @@ static void routing(HttpRules routing) {
5758
&& data.sourceAddress().equals("192.168.0.1")
5859
&& data.destAddress().equals("192.168.0.11")
5960
&& data.sourcePort() == 56324
60-
&& data.destPort() == 443) {
61+
&& data.destPort() == 443
62+
&& "192.168.0.1".equals(req.headers().first(HeaderNames.X_FORWARDED_FOR).orElse(null))
63+
&& "56324".equals(req.headers().first(HeaderNames.X_FORWARDED_PORT).orElse(null))) {
6164
res.status(Status.OK_200).send();
6265
return;
6366
}

webserver/webserver/src/main/java/io/helidon/webserver/ConnectionContext.java

+1-2
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
import io.helidon.common.buffers.DataReader;
2323
import io.helidon.common.buffers.DataWriter;
2424
import io.helidon.common.socket.SocketContext;
25-
import io.helidon.common.socket.SocketOptions;
2625

2726
/**
2827
* Server connection context.
@@ -67,7 +66,7 @@ public interface ConnectionContext extends SocketContext {
6766
* Proxy protocol header data.
6867
*
6968
* @return protocol header data if proxy protocol is enabled on socket
70-
* @see SocketOptions#enableProxyProtocol()
69+
* @see ListenerConfig#enableProxyProtocol()
7170
*/
7271
default Optional<ProxyProtocolData> proxyProtocolData() {
7372
return Optional.empty();

webserver/webserver/src/main/java/io/helidon/webserver/ProxyProtocolData.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -101,14 +101,14 @@ static Protocol fromString(String s) {
101101
/**
102102
* Source address that is either IP4 or IP6 depending on {@link #family()}.
103103
*
104-
* @return source address
104+
* @return source address or {@code ""} if not provided
105105
*/
106106
String sourceAddress();
107107

108108
/**
109109
* Destination address that is either IP4 or IP46 depending on {@link #family()}.
110110
*
111-
* @return source address
111+
* @return source address or (@code ""} if not provided
112112
*/
113113
String destAddress();
114114

webserver/webserver/src/main/java/io/helidon/webserver/ProxyProtocolHandler.java

+3-3
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ static ProxyProtocolData handleV1Protocol(PushbackInputStream inputStream) throw
116116
// special case for just UNKNOWN family
117117
if (family == ProxyProtocolData.Family.UNKNOWN) {
118118
return new ProxyProtocolDataImpl(Family.UNKNOWN, Protocol.UNKNOWN,
119-
null, null, -1, -1);
119+
"", "", -1, -1);
120120
}
121121
}
122122

@@ -178,8 +178,8 @@ static ProxyProtocolData handleV2Protocol(PushbackInputStream inputStream) throw
178178
int headerLength = ((b << 8) & 0xFF00) | (readNext(inputStream) & 0xFF);
179179

180180
// decode addresses and ports
181-
String sourceAddress = null;
182-
String destAddress = null;
181+
String sourceAddress = "";
182+
String destAddress = "";
183183
int sourcePort = -1;
184184
int destPort = -1;
185185
switch (family) {

webserver/webserver/src/main/java/io/helidon/webserver/http/ServerRequest.java

+1-2
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
import java.util.function.UnaryOperator;
2222

2323
import io.helidon.common.context.Context;
24-
import io.helidon.common.socket.SocketOptions;
2524
import io.helidon.http.RoutedPath;
2625
import io.helidon.http.media.ReadableEntity;
2726
import io.helidon.webserver.ListenerContext;
@@ -118,7 +117,7 @@ public interface ServerRequest extends HttpRequest {
118117
* Access proxy protocol data for the connection on which this request was sent.
119118
*
120119
* @return proxy protocol data, if available
121-
* @see SocketOptions#enableProxyProtocol()
120+
* @see io.helidon.webserver.ListenerConfig#enableProxyProtocol()
122121
*/
123122
Optional<ProxyProtocolData> proxyProtocolData();
124123
}

webserver/webserver/src/main/java/io/helidon/webserver/http1/Http1Connection.java

+18
Original file line numberDiff line numberDiff line change
@@ -47,11 +47,14 @@
4747
import io.helidon.http.encoding.ContentEncodingContext;
4848
import io.helidon.webserver.CloseConnectionException;
4949
import io.helidon.webserver.ConnectionContext;
50+
import io.helidon.webserver.ProxyProtocolData;
5051
import io.helidon.webserver.http.DirectTransportRequest;
5152
import io.helidon.webserver.http.HttpRouting;
5253
import io.helidon.webserver.http1.spi.Http1Upgrader;
5354
import io.helidon.webserver.spi.ServerConnection;
5455

56+
import static io.helidon.http.HeaderNames.X_FORWARDED_FOR;
57+
import static io.helidon.http.HeaderNames.X_FORWARDED_PORT;
5558
import static io.helidon.http.HeaderNames.X_HELIDON_CN;
5659
import static java.lang.System.Logger.Level.TRACE;
5760
import static java.lang.System.Logger.Level.WARNING;
@@ -128,6 +131,9 @@ public boolean canInterrupt() {
128131
public void handle(Semaphore requestSemaphore) throws InterruptedException {
129132
this.myThread = Thread.currentThread();
130133
try {
134+
// look for protocol data
135+
ProxyProtocolData proxyProtocolData = ctx.proxyProtocolData().orElse(null);
136+
131137
// handle connection until an exception (or explicit connection close)
132138
while (canRun) {
133139
// prologue (first line of request)
@@ -145,6 +151,18 @@ public void handle(Semaphore requestSemaphore) throws InterruptedException {
145151
.ifPresent(name -> headers.set(X_HELIDON_CN, name));
146152
recvListener.headers(ctx, headers);
147153

154+
// proxy protocol related headers X-Forwarded-For and X-Forwarded-Port
155+
if (proxyProtocolData != null) {
156+
String sourceAddress = proxyProtocolData.sourceAddress();
157+
if (!sourceAddress.isEmpty()) {
158+
headers.add(X_FORWARDED_FOR, sourceAddress);
159+
}
160+
int sourcePort = proxyProtocolData.sourcePort();
161+
if (sourcePort != -1) {
162+
headers.add(X_FORWARDED_PORT, sourcePort);
163+
}
164+
}
165+
148166
if (canUpgrade) {
149167
if (headers.contains(HeaderNames.UPGRADE)) {
150168
Http1Upgrader upgrader = upgradeProviderMap.get(headers.get(HeaderNames.UPGRADE).get());

webserver/webserver/src/test/java/io/helidon/webserver/ProxyProtocolHandlerTest.java

+4-5
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
import org.junit.jupiter.api.Test;
2525

2626
import static org.hamcrest.CoreMatchers.is;
27-
import static org.hamcrest.CoreMatchers.nullValue;
2827
import static org.hamcrest.MatcherAssert.assertThat;
2928
import static org.junit.jupiter.api.Assertions.assertThrows;
3029
import static io.helidon.common.testing.junit5.HexStringDecoder.decodeHexString;
@@ -53,8 +52,8 @@ void unknownV1Test() throws IOException {
5352
new ByteArrayInputStream(header.getBytes(StandardCharsets.US_ASCII))));
5453
assertThat(data.family(), is(ProxyProtocolData.Family.UNKNOWN));
5554
assertThat(data.protocol(), is(ProxyProtocolData.Protocol.UNKNOWN));
56-
assertThat(data.sourceAddress(), nullValue());
57-
assertThat(data.destAddress(), nullValue());
55+
assertThat(data.sourceAddress(), is(""));
56+
assertThat(data.destAddress(), is(""));
5857
assertThat(data.sourcePort(), is(-1));
5958
assertThat(data.destPort(), is(-1));
6059
}
@@ -133,8 +132,8 @@ void unknownV2Test() throws IOException {
133132
new ByteArrayInputStream(decodeHexString(header))));
134133
assertThat(data.family(), is(ProxyProtocolData.Family.UNKNOWN));
135134
assertThat(data.protocol(), is(ProxyProtocolData.Protocol.UNKNOWN));
136-
assertThat(data.sourceAddress(), nullValue());
137-
assertThat(data.destAddress(), nullValue());
135+
assertThat(data.sourceAddress(), is(""));
136+
assertThat(data.destAddress(), is(""));
138137
assertThat(data.sourcePort(), is(-1));
139138
assertThat(data.destPort(), is(-1));
140139
}

0 commit comments

Comments
 (0)