Skip to content

Commit 6e7a84b

Browse files
author
dmitrii
committed
Avoid running the IPv6 regex on IPv4:port in normalizeIp
normalizeIp() called the version-less IPValidator.isIP(ip) first, which runs the expensive IPv6 regex on every value that is not a bare IPv4 — including IPv4:port forms that proxies routinely send in X-Forwarded-For / the peer address. Since this runs in the per-request context constructor, it added CPU on proxied traffic (introduced in #307). Reorder normalizeIp to run the cheap structural checks before the version-less isIP, keeping the same checks (and branch bodies) as zen-node's getClientIpFromHeader: single-colon IPv4:port via isIP(host, "4"), then bracketed IPv6 ([ipv6] / [ipv6]:port), and only then the version-less isIP as the final fallback. node's isIP is native and cheap, ours is a regex, so the only deviation from node is running isIP last instead of first. Behavior is unchanged (existing ProxyForwardedParserTest suite passes; added cases for multi-colon IPv4, IPv4:port short-circuit, bracketed IPv4 and malformed "[::1]foo"). Verified with a JFR A/B on zen-demo-java under ip:port traffic: total agent CPU 4.90% -> 4.41% (baseline before the regression 4.53%).
1 parent 6ee5432 commit 6e7a84b

2 files changed

Lines changed: 36 additions & 8 deletions

File tree

agent_api/src/main/java/dev/aikido/agent_api/helpers/net/ProxyForwardedParser.java

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -55,17 +55,23 @@ private static String normalizeIp(String ip) {
5555

5656
ip = ip.trim();
5757

58-
if (IPValidator.isIP(ip)) {
59-
return ip;
58+
// Some proxies pass along port numbers with IPv4 addresses: ip:port
59+
if (ip.contains(":")) {
60+
String[] ipParts = ip.split(":");
61+
if (ipParts.length == 2 && IPValidator.isIP(ipParts[0], "4")) {
62+
return ipParts[0];
63+
}
6064
}
6165

66+
// Normalize IPv6 without port by removing brackets: [ipv6]
6267
if (ip.startsWith("[") && ip.endsWith("]")) {
6368
String unwrappedIp = ip.substring(1, ip.length() - 1);
6469
if (IPValidator.isIP(unwrappedIp)) {
6570
return unwrappedIp;
6671
}
6772
}
6873

74+
// IPv6 with port: [ipv6]:port
6975
if (ip.startsWith("[")) {
7076
int closingBracket = ip.indexOf("]:");
7177
if (closingBracket > 0) {
@@ -76,12 +82,8 @@ private static String normalizeIp(String ip) {
7682
}
7783
}
7884

79-
// Some proxies pass along port numbers with IP addresses :
80-
if (ip.contains(":")) {
81-
String[] ipParts = ip.split(":");
82-
if (ipParts.length == 2 && IPValidator.isIP(ipParts[0], "4")) {
83-
return ipParts[0];
84-
}
85+
if (IPValidator.isIP(ip)) {
86+
return ip;
8587
}
8688

8789
return null;

agent_api/src/test/java/helpers/ProxyForwardedParserTest.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,4 +214,30 @@ void testGetIpFromRequest_RawIpWithEmptyStringFallsBackToRawIp() {
214214
String result = getIpFromRequest("", new HashMap<>());
215215
assertEquals("", result);
216216
}
217+
218+
@Test
219+
void testGetIpFromRequest_IPv4WithMultiplePortColonsFallsBackToRawIp() {
220+
headers.put("X-Forwarded-For", List.of("1.2.3.4:80:90"));
221+
String result = getIpFromRequest("10.0.0.1", headers);
222+
assertEquals("10.0.0.1", result);
223+
}
224+
225+
@Test
226+
void testGetIpFromRequest_IPv4PortResolvesWithoutTouchingLaterEntries() {
227+
headers.put("X-Forwarded-For", List.of("203.0.113.7:54321, 8.8.8.8"));
228+
String result = getIpFromRequest("10.0.0.1", headers);
229+
assertEquals("203.0.113.7", result);
230+
}
231+
232+
@Test
233+
void testGetIpFromRequest_BracketedIPv4Resolves() {
234+
String result = getIpFromRequest("[1.2.3.4]", new HashMap<>());
235+
assertEquals("1.2.3.4", result);
236+
}
237+
238+
@Test
239+
void testGetIpFromRequest_BracketedIpWithTrailingGarbageFallsBackToRawIp() {
240+
String result = getIpFromRequest("[::1]foo", new HashMap<>());
241+
assertEquals("[::1]foo", result);
242+
}
217243
}

0 commit comments

Comments
 (0)