-
Notifications
You must be signed in to change notification settings - Fork 74
Aggregate HA proxy line before parsing it #286
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
andsel
merged 7 commits into
logstash-plugins:main
from
andsel:fix/aggregate_proxy_line_before_parsing
Sep 7, 2026
Merged
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
df80360
[Test] Update test to try to reproduce issue of proxy parsing on not …
andsel 3184ecb
Implemented HAProxy lines aggregator at Netty level
andsel c361409
[Test] offloaded in side thread the write and flush of part of the HA…
andsel ea983d5
Add the proxy line aggreagtor codec to the Netty pipelines used on cl…
andsel aceb5e4
[Test] Add explicit fail if Queue.pop timeout elapses
andsel 2ced7a1
Apply batched suggestions from code review
andsel b8266b2
Simplified the ProxyLineAggregator, when the accumulated line match t…
andsel File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,52 @@ | ||
| package org.logstash.tcp; | ||
|
|
||
| import io.netty.buffer.ByteBuf; | ||
| import io.netty.channel.ChannelHandlerContext; | ||
| import io.netty.handler.codec.ByteToMessageDecoder; | ||
| import io.netty.util.ByteProcessor; | ||
|
|
||
| import java.nio.charset.StandardCharsets; | ||
| import java.util.List; | ||
|
|
||
|
|
||
| /** | ||
| * The line has format "PROXY....\r\n"; this aggregator holds back the buffer until that full | ||
| * line is available, then passes it through and removes itself from the pipeline. | ||
| * This is needed because Ruby class DecoderImpl expects the full line when processing the HAProxy | ||
| * protocol and doesn't work with fragments. | ||
| * */ | ||
| public class ProxyLineAggregator extends ByteToMessageDecoder { | ||
|
andsel marked this conversation as resolved.
|
||
|
|
||
| private static final byte[] PROXY_PREFIX = "PROXY".getBytes(StandardCharsets.US_ASCII); | ||
| public static final int PROXY_LENGTH = PROXY_PREFIX.length; | ||
|
|
||
| @Override | ||
| protected void decode(ChannelHandlerContext ctx, ByteBuf buffer, List<Object> out) throws Exception { | ||
| // Wait until we can decide: enough bytes to match the prefix, and if it is a PROXY | ||
| // line, the terminating \r\n must be present. | ||
| if (buffer.readableBytes() < PROXY_LENGTH) { | ||
| return; | ||
| } | ||
| if (startsWithProxy(buffer) && !containsCrlf(buffer)) { | ||
| return; | ||
| } | ||
| // Full PROXY line, or non-PROXY data: pass everything through and drop this handler | ||
| // so subsequent reads skip the aggregator entirely. | ||
| out.add(buffer.readRetainedSlice(buffer.readableBytes())); | ||
| ctx.pipeline().remove(this); | ||
| } | ||
|
|
||
| private static boolean containsCrlf(ByteBuf buffer) { | ||
| int lfIndex = buffer.forEachByte(ByteProcessor.FIND_LF); | ||
| return lfIndex > 0 && buffer.getByte(lfIndex - 1) == '\r'; | ||
| } | ||
|
|
||
| private static boolean startsWithProxy(ByteBuf buffer) { | ||
| for (int i = 0; i < PROXY_LENGTH; i++) { | ||
| if (buffer.getByte(buffer.readerIndex() + i) != PROXY_PREFIX[i]) { | ||
| return false; | ||
| } | ||
| } | ||
| return true; | ||
| } | ||
| } | ||
65 changes: 65 additions & 0 deletions
65
src/test/java/org/logstash/tcp/ProxyLineAggregatorTest.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,65 @@ | ||
| package org.logstash.tcp; | ||
|
|
||
| import io.netty.buffer.ByteBuf; | ||
| import io.netty.buffer.Unpooled; | ||
| import io.netty.channel.embedded.EmbeddedChannel; | ||
| import org.junit.jupiter.api.AfterEach; | ||
| import org.junit.jupiter.api.BeforeEach; | ||
| import org.junit.jupiter.api.Test; | ||
|
|
||
| import java.nio.charset.StandardCharsets; | ||
|
|
||
| import static org.hamcrest.MatcherAssert.assertThat; | ||
| import static org.hamcrest.Matchers.notNullValue; | ||
| import static org.hamcrest.Matchers.nullValue; | ||
|
|
||
| class ProxyLineAggregatorTest { | ||
|
|
||
| private EmbeddedChannel channel; | ||
|
|
||
| @BeforeEach | ||
| void setUp() { | ||
| channel = new EmbeddedChannel(new ProxyLineAggregator()); | ||
| } | ||
|
|
||
| @AfterEach | ||
| void tearDown() { | ||
| channel.finishAndReleaseAll(); | ||
| } | ||
|
|
||
| private static ByteBuf ascii(String s) { | ||
| return Unpooled.copiedBuffer(s, StandardCharsets.US_ASCII); | ||
| } | ||
|
|
||
| @Test | ||
| void partialProxyHeader_producesNoOutput() { | ||
| // "PRO" is shorter than the 5-byte "PROXY" prefix — decoder must wait | ||
| channel.writeInbound(ascii("PRO")); | ||
| assertThat(channel.readInbound(), nullValue()); | ||
| } | ||
|
|
||
| @Test | ||
| void proxyPrefixWithoutCRLF_producesNoOutput() { | ||
| // Full PROXY keyword is present but the line terminator \r\n is missing | ||
| channel.writeInbound(ascii("PROXY TCP4 192.168.1.1 10.0.0.1 1234 80")); | ||
| assertThat(channel.readInbound(), nullValue()); | ||
| } | ||
|
|
||
| @Test | ||
| void nonProxyDataWithSufficientBytes_passesThrough() { | ||
| // Content >= PROXY_LENGTH bytes but not a PROXY header and no \r\n: | ||
| // the aggregator should recognise it is not a PROXY line and let it through | ||
| channel.writeInbound(ascii("Hello, World!")); | ||
| ByteBuf result = channel.readInbound(); | ||
| assertThat(result, notNullValue()); | ||
| result.release(); | ||
| } | ||
|
|
||
| @Test | ||
| void completeProxyLineWithCRLF_forwardsBuffer() { | ||
| channel.writeInbound(ascii("PROXY TCP4 192.168.1.1 10.0.0.1 1234 80\r\n")); | ||
| ByteBuf result = channel.readInbound(); | ||
| assertThat(result, notNullValue()); | ||
| result.release(); | ||
| } | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.