Skip to content

Commit d582239

Browse files
committed
Fix the handling on local mode in AwsProxyEncoderHelper
1 parent 07ee29c commit d582239

6 files changed

Lines changed: 105 additions & 127 deletions

File tree

proxy-socket-core/src/main/java/net/airvantage/proxysocket/core/v2/ProxyProtocolV2Decoder.java

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -65,17 +65,11 @@ public static ProxyHeader parse(byte[] data, int offset, int length, boolean par
6565
throw new ProxyProtocolParseException("Invalid version");
6666
}
6767

68-
Command command;
69-
switch (cmd) {
70-
case 0x00:
71-
// Early return for LOCAL command
72-
return new ProxyHeader(Command.LOCAL, AddressFamily.AF_UNSPEC, TransportProtocol.UNSPEC, null, null, null, PROTOCOL_SIGNATURE_FIXED_LENGTH);
73-
case 0x01:
74-
command = Command.PROXY;
75-
break;
76-
default:
77-
throw new ProxyProtocolParseException("Invalid command");
78-
}
68+
Command command = switch (cmd) {
69+
case 0x00 -> Command.LOCAL;
70+
case 0x01 -> Command.PROXY;
71+
default -> throw new ProxyProtocolParseException("Invalid command");
72+
};
7973

8074
// Byte 14: address family and protocol
8175
int famProto = data[pos++] & 0xFF;

proxy-socket-core/src/test/java/net/airvantage/proxysocket/core/v2/AwsProxyEncoderHelper.java

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,18 @@ public final class AwsProxyEncoderHelper {
2828
private final Header header = new Header();
2929

3030
public AwsProxyEncoderHelper command(ProxyHeader.Command cmd) {
31-
this.command = cmd == ProxyHeader.Command.LOCAL
32-
? ProxyProtocolSpec.Command.LOCAL
33-
: ProxyProtocolSpec.Command.PROXY;
31+
if (cmd == ProxyHeader.Command.LOCAL) {
32+
this.command = ProxyProtocolSpec.Command.LOCAL;
33+
34+
// Spec clearly state that for LOCAL command, we
35+
// 1. must discard the protocol block including the family and
36+
// 2. \x00 is expected to be used for the protocol field.
37+
this.family = ProxyProtocolSpec.AddressFamily.AF_UNSPEC;
38+
this.protocol = ProxyProtocolSpec.TransportProtocol.UNSPEC;
39+
} else {
40+
this.command = ProxyProtocolSpec.Command.PROXY;
41+
}
42+
3443
return this;
3544
}
3645

@@ -76,13 +85,7 @@ public byte[] build() throws IOException {
7685
header.setAddressFamily(family);
7786
header.setTransportProtocol(protocol);
7887

79-
// AWS ProProt validates addresses even for LOCAL command, set dummy values
80-
if (command == ProxyProtocolSpec.Command.LOCAL && source == null) {
81-
header.setSrcAddress(new byte[]{0, 0, 0, 0});
82-
header.setDstAddress(new byte[]{0, 0, 0, 0});
83-
header.setSrcPort(0);
84-
header.setDstPort(0);
85-
} else {
88+
if (command != ProxyProtocolSpec.Command.LOCAL) {
8689
if (source != null) {
8790
header.setSrcAddress(source.getAddress().getAddress());
8891
header.setSrcPort(source.getPort());

proxy-socket-udp/src/main/java/net/airvantage/proxysocket/udp/ProxyDatagramSocket.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ public void receive(DatagramPacket packet)
101101
}
102102

103103
int headerLen = header.getHeaderLength();
104-
LOG.trace("Stripping header: {} bytes, remaining length: {}", headerLen, packet.getLength() - headerLen);
104+
LOG.trace("Stripping header: {} bytes, original length: {}", headerLen, packet.getLength());
105105
packet.setData(packet.getData(), packet.getOffset() + headerLen, packet.getLength() - headerLen);
106106
} catch (ProxyProtocolParseException e) {
107107
LOG.warn("Proxy socket parse error; delivering original packet.", e);

proxy-socket-udp/src/test/java/net/airvantage/proxysocket/udp/ProxyDatagramSocketTest.java

Lines changed: 44 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -21,23 +21,33 @@
2121
import static org.junit.jupiter.api.Assertions.*;
2222
import static org.mockito.Mockito.*;
2323

24+
import org.slf4j.Logger;
25+
import org.slf4j.LoggerFactory;
2426
/**
2527
* Unit tests for ProxyDatagramSocket IP address mapping and cache behavior.
2628
*/
27-
class ProxyDatagramSocketIPMappingTest {
29+
class ProxyDatagramSocketTest {
30+
private static final Logger LOG = LoggerFactory.getLogger(ProxyDatagramSocket.class);
2831

2932
private ProxyDatagramSocket socket;
3033
private ProxyAddressCache mockCache;
3134
private ProxyProtocolMetricsListener mockMetrics;
35+
36+
private InetSocketAddress realClient;
37+
private InetSocketAddress serviceAddress;
38+
private InetSocketAddress backendAddress;
3239
private int localPort;
3340

3441
@BeforeEach
3542
void setUp() throws Exception {
3643
mockCache = mock(ProxyAddressCache.class);
3744
mockMetrics = mock(ProxyProtocolMetricsListener.class);
3845

46+
realClient = new InetSocketAddress(InetAddress.getLoopbackAddress(), 12345);
47+
serviceAddress = new InetSocketAddress(InetAddress.getLoopbackAddress(), 54321);
3948
socket = new ProxyDatagramSocket(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0), mockCache, mockMetrics, null);
4049
localPort = socket.getLocalPort();
50+
backendAddress = new InetSocketAddress(InetAddress.getLoopbackAddress(), localPort);
4151
}
4252

4353
@AfterEach
@@ -48,27 +58,24 @@ void tearDown() {
4858
}
4959

5060
@Test
51-
void receive_withValidProxyHeader_populatesCache() throws Exception {
61+
void receive_withValidProxyHeader() throws Exception {
5262
// Arrange
53-
InetSocketAddress realClient = new InetSocketAddress("10.1.2.3", 12345);
54-
InetSocketAddress lbAddress = new InetSocketAddress("127.0.0.1", 54321);
5563
byte[] payload = "test-data".getBytes(StandardCharsets.UTF_8);
5664

5765
var proxyHeader = new AwsProxyEncoderHelper()
5866
.family(ProxyHeader.AddressFamily.AF_INET)
5967
.socket(ProxyHeader.TransportProtocol.DGRAM)
6068
.source(realClient)
61-
.destination(new InetSocketAddress("127.0.0.1", localPort))
69+
.destination(serviceAddress)
6270
.build();
6371

6472
byte[] packet = new byte[proxyHeader.length + payload.length];
6573
System.arraycopy(proxyHeader, 0, packet, 0, proxyHeader.length);
6674
System.arraycopy(payload, 0, packet, proxyHeader.length, payload.length);
6775

6876
// Create a loopback socket to send from
69-
try (java.net.DatagramSocket sender = new java.net.DatagramSocket(lbAddress)) {
70-
sender.send(new DatagramPacket(packet, packet.length,
71-
new InetSocketAddress("127.0.0.1", localPort)));
77+
try (java.net.DatagramSocket sender = new java.net.DatagramSocket(serviceAddress)) {
78+
sender.send(new DatagramPacket(packet, packet.length, backendAddress));
7279
}
7380

7481
// Act
@@ -82,8 +89,8 @@ void receive_withValidProxyHeader_populatesCache() throws Exception {
8289
verify(mockCache).put(clientCaptor.capture(), lbCaptor.capture());
8390

8491
assertEquals(realClient, clientCaptor.getValue());
85-
assertEquals(lbAddress.getAddress(), lbCaptor.getValue().getAddress());
86-
assertEquals(lbAddress.getPort(), lbCaptor.getValue().getPort());
92+
assertEquals(serviceAddress.getAddress(), lbCaptor.getValue().getAddress());
93+
assertEquals(serviceAddress.getPort(), lbCaptor.getValue().getPort());
8794

8895
// Verify packet was modified to show real client address
8996
assertEquals(realClient, receivePacket.getSocketAddress());
@@ -96,24 +103,21 @@ void receive_withValidProxyHeader_populatesCache() throws Exception {
96103
receivePacket.getOffset() + receivePacket.getLength()));
97104

98105
verify(mockMetrics).onHeaderParsed(any(ProxyHeader.class));
99-
verify(mockMetrics).onTrustedProxy(lbAddress.getAddress());
106+
verify(mockMetrics).onTrustedProxy(serviceAddress.getAddress());
100107
verify(mockMetrics, never()).onUntrustedProxy(any());
101108
verify(mockMetrics, never()).onParseError(any());
102109
verify(mockMetrics, never()).onLocal(any());
103110
}
104111

105112
@Test
106113
void send_withCacheHit_usesLoadBalancerAddress() throws Exception {
107-
// Arrange
108-
InetSocketAddress realClient = new InetSocketAddress("10.1.2.3", 12345);
109-
InetSocketAddress lbAddress = new InetSocketAddress("127.0.0.1", 54321);
110114
byte[] payload = "response".getBytes(StandardCharsets.UTF_8);
111115

112116
// Mock cache to return lb address
113-
when(mockCache.get(realClient)).thenReturn(lbAddress);
117+
when(mockCache.get(realClient)).thenReturn(serviceAddress);
114118

115119
// Create a receiver to verify the packet destination
116-
java.net.DatagramSocket receiver = new java.net.DatagramSocket(lbAddress);
120+
java.net.DatagramSocket receiver = new java.net.DatagramSocket(serviceAddress);
117121
receiver.setSoTimeout(1000);
118122

119123
try {
@@ -142,37 +146,35 @@ void send_withCacheHit_usesLoadBalancerAddress() throws Exception {
142146
}
143147

144148
@Test
145-
void send_withCacheMiss_usesOriginalAddress() throws Exception {
149+
void send_withCacheMiss_dropsPacket() throws Exception {
146150
// Arrange
147-
InetSocketAddress clientAddress = new InetSocketAddress("127.0.0.1", 55555);
148151
byte[] payload = "response".getBytes(StandardCharsets.UTF_8);
149152

150153
// Mock cache to return null (cache miss)
151-
when(mockCache.get(clientAddress)).thenReturn(null);
154+
when(mockCache.get(realClient)).thenReturn(null);
152155

153-
// Create a receiver at the client address
154-
java.net.DatagramSocket receiver = new java.net.DatagramSocket(clientAddress);
155-
receiver.setSoTimeout(1000);
156+
// Create a receiver at the client address to verify packet is NOT sent
157+
java.net.DatagramSocket receiver = new java.net.DatagramSocket(realClient);
158+
receiver.setSoTimeout(500); // Short timeout since we expect no packet
156159

157160
try {
158161
// Act - send to client address
159-
DatagramPacket sendPacket = new DatagramPacket(payload, payload.length, clientAddress);
162+
DatagramPacket sendPacket = new DatagramPacket(payload, payload.length, realClient);
160163
socket.send(sendPacket);
161164

162-
// Verify packet was sent to original address
165+
// Try to receive - should timeout since packet was dropped
163166
byte[] receiveBuf = new byte[2048];
164167
DatagramPacket receivePacket = new DatagramPacket(receiveBuf, receiveBuf.length);
165-
receiver.receive(receivePacket);
166168

167-
// Assert
168-
assertArrayEquals(payload,
169-
java.util.Arrays.copyOfRange(receivePacket.getData(), 0, receivePacket.getLength()));
169+
assertThrows(java.net.SocketTimeoutException.class, () -> {
170+
receiver.receive(receivePacket);
171+
}, "Expected packet to be dropped on cache miss");
170172

171173
// Verify cache was queried
172-
verify(mockCache).get(clientAddress);
174+
verify(mockCache).get(realClient);
173175

174176
// Verify metrics - cache miss
175-
verify(mockMetrics).onCacheMiss(clientAddress);
177+
verify(mockMetrics).onCacheMiss(realClient);
176178
verify(mockMetrics, never()).onCacheHit(any());
177179
} finally {
178180
receiver.close();
@@ -187,20 +189,24 @@ void receive_withLocalCommand_doesNotPopulateCache() throws Exception {
187189
.command(ProxyHeader.Command.LOCAL)
188190
.build();
189191

192+
LOG.trace("Payload:\n{}", Utility.hexdump(payload, 0, payload.length));
193+
LOG.trace("Proxy header:\n{}", Utility.hexdump(proxyHeader, 0, proxyHeader.length));
194+
190195
byte[] packet = new byte[proxyHeader.length + payload.length];
191196
System.arraycopy(proxyHeader, 0, packet, 0, proxyHeader.length);
192197
System.arraycopy(payload, 0, packet, proxyHeader.length, payload.length);
193198

194199
try (java.net.DatagramSocket sender = new java.net.DatagramSocket()) {
195-
sender.send(new DatagramPacket(packet, packet.length,
196-
new InetSocketAddress("127.0.0.1", localPort)));
200+
sender.send(new DatagramPacket(packet, packet.length, backendAddress));
197201
}
198202

199203
// Act
200204
byte[] receiveBuf = new byte[2048];
201205
DatagramPacket receivePacket = new DatagramPacket(receiveBuf, receiveBuf.length);
202206
socket.receive(receivePacket);
203207

208+
LOG.trace("Received packet length={}, content:\n{}", receivePacket.getLength(), Utility.hexdump(receivePacket.getData(), receivePacket.getOffset(), receivePacket.getLength()));
209+
204210
// Assert - cache should NOT be populated for LOCAL commands
205211
verify(mockCache, never()).put(any(), any());
206212

@@ -218,17 +224,16 @@ void receive_withTcpProtocol_doesNotPopulateCache() throws Exception {
218224
byte[] proxyHeader = new AwsProxyEncoderHelper()
219225
.family(ProxyHeader.AddressFamily.AF_INET)
220226
.socket(ProxyHeader.TransportProtocol.STREAM) // TCP, not UDP
221-
.source(new InetSocketAddress("10.1.2.3", 12345))
222-
.destination(new InetSocketAddress("127.0.0.1", localPort))
227+
.source(realClient)
228+
.destination(serviceAddress)
223229
.build();
224230

225231
byte[] packet = new byte[proxyHeader.length + payload.length];
226232
System.arraycopy(proxyHeader, 0, packet, 0, proxyHeader.length);
227233
System.arraycopy(payload, 0, packet, proxyHeader.length, payload.length);
228234

229235
try (java.net.DatagramSocket sender = new java.net.DatagramSocket()) {
230-
sender.send(new DatagramPacket(packet, packet.length,
231-
new InetSocketAddress("127.0.0.1", localPort)));
236+
sender.send(new DatagramPacket(packet, packet.length, backendAddress));
232237
}
233238

234239
// Act
@@ -247,14 +252,13 @@ void receive_withTcpProtocol_doesNotPopulateCache() throws Exception {
247252
@Test
248253
void receive_withValidProxyHeader_callsMetricsOnHeaderParsed() throws Exception {
249254
// Arrange
250-
InetSocketAddress realClient = new InetSocketAddress("10.1.2.3", 12345);
251255
byte[] payload = "test".getBytes(StandardCharsets.UTF_8);
252256

253257
byte[] proxyHeader = new AwsProxyEncoderHelper()
254258
.family(ProxyHeader.AddressFamily.AF_INET)
255259
.socket(ProxyHeader.TransportProtocol.DGRAM)
256260
.source(realClient)
257-
.destination(new InetSocketAddress("127.0.0.1", localPort))
261+
.destination(serviceAddress)
258262
.build();
259263

260264
byte[] packet = new byte[proxyHeader.length + payload.length];
@@ -263,8 +267,7 @@ void receive_withValidProxyHeader_callsMetricsOnHeaderParsed() throws Exception
263267

264268
// Send packet
265269
try (java.net.DatagramSocket sender = new java.net.DatagramSocket()) {
266-
sender.send(new DatagramPacket(packet, packet.length,
267-
new InetSocketAddress("127.0.0.1", localPort)));
270+
sender.send(new DatagramPacket(packet, packet.length, backendAddress));
268271
}
269272

270273
// Act
@@ -288,8 +291,7 @@ void receive_withInvalidData_callsMetricsOnParseError() throws Exception {
288291
byte[] garbage = "not-a-proxy-header".getBytes(StandardCharsets.UTF_8);
289292

290293
try (java.net.DatagramSocket sender = new java.net.DatagramSocket()) {
291-
sender.send(new DatagramPacket(garbage, garbage.length,
292-
new InetSocketAddress("127.0.0.1", localPort)));
294+
sender.send(new DatagramPacket(garbage, garbage.length, backendAddress));
293295
}
294296

295297
// Act
@@ -304,67 +306,5 @@ void receive_withInvalidData_callsMetricsOnParseError() throws Exception {
304306
assertEquals(garbage.length, receivePacket.getLength());
305307
}
306308

307-
@Test
308-
void send_withCacheHit_callsMetricsOnCacheHit() throws Exception {
309-
// Arrange
310-
InetSocketAddress realClient = new InetSocketAddress("10.1.2.3", 12345);
311-
InetSocketAddress lbAddress = new InetSocketAddress("127.0.0.1", 54321);
312-
byte[] payload = "response".getBytes(StandardCharsets.UTF_8);
313-
314-
// Mock cache to return lb address
315-
when(mockCache.get(realClient)).thenReturn(lbAddress);
316-
317-
// Create a receiver to verify the packet destination
318-
java.net.DatagramSocket receiver = new java.net.DatagramSocket(lbAddress);
319-
receiver.setSoTimeout(1000);
320-
321-
try {
322-
// Act - send to real client, should be redirected to LB
323-
DatagramPacket sendPacket = new DatagramPacket(payload, payload.length, realClient);
324-
socket.send(sendPacket);
325-
326-
// Receive the packet (to avoid timeout)
327-
byte[] receiveBuf = new byte[2048];
328-
DatagramPacket receivePacket = new DatagramPacket(receiveBuf, receiveBuf.length);
329-
receiver.receive(receivePacket);
330-
331-
// Assert - onCacheHit should be called
332-
verify(mockMetrics).onCacheHit(realClient);
333-
verify(mockMetrics, never()).onCacheMiss(any());
334-
} finally {
335-
receiver.close();
336-
}
337-
}
338-
339-
@Test
340-
void send_withCacheMiss_callsMetricsOnCacheMiss() throws Exception {
341-
// Arrange
342-
InetSocketAddress clientAddress = new InetSocketAddress("127.0.0.1", 55555);
343-
byte[] payload = "response".getBytes(StandardCharsets.UTF_8);
344-
345-
// Mock cache to return null (cache miss)
346-
when(mockCache.get(clientAddress)).thenReturn(null);
347-
348-
// Create a receiver at the client address
349-
java.net.DatagramSocket receiver = new java.net.DatagramSocket(clientAddress);
350-
receiver.setSoTimeout(1000);
351-
352-
try {
353-
// Act - send to client address
354-
DatagramPacket sendPacket = new DatagramPacket(payload, payload.length, clientAddress);
355-
socket.send(sendPacket);
356-
357-
// Receive the packet (to avoid timeout)
358-
byte[] receiveBuf = new byte[2048];
359-
DatagramPacket receivePacket = new DatagramPacket(receiveBuf, receiveBuf.length);
360-
receiver.receive(receivePacket);
361-
362-
// Assert - onCacheMiss should be called
363-
verify(mockMetrics).onCacheMiss(clientAddress);
364-
verify(mockMetrics, never()).onCacheHit(any());
365-
} finally {
366-
receiver.close();
367-
}
368-
}
369309
}
370310

proxy-socket-udp/src/test/java/net/airvantage/proxysocket/udp/ProxyDatagramSocketUnTrustedProxyTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
/**
2626
* Unit tests for ProxyDatagramSocket with untrusted proxy source.
2727
*/
28-
class ProxyDatagramSocketMetricsTest {
28+
class ProxyDatagramSocketUnTrustedProxyTest {
2929

3030
private ProxyDatagramSocket socket;
3131
private ProxyAddressCache mockCache;

0 commit comments

Comments
 (0)