Skip to content

Commit d1aaf41

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 noticeable CPU on proxied traffic. Reorder normalizeIp to do the cheap structural checks first: - bracketed IPv6 ([ipv6] / [ipv6]:port) handled up front, - single-colon IPv4:port resolved via isIP(host, "4") (IPv4 regex only), - only then fall back to the version-less isIP for bare IPv4/IPv6. Behavior is unchanged (the existing ProxyForwardedParserTest suite passes); this only removes a redundant IPv6 regex evaluation on IPv4:port values. Verified with a JFR A/B on the zen-demo-java app under load (ip:port XFF): IP-parsing CPU share drops back from ~1.45% to ~0.9% and total agent CPU from 4.90% to 4.61% (baseline before the regression was 4.53%). Microbenchmark: IPv4:port getIpFromRequest 722ns -> 159ns.
1 parent 6ee5432 commit d1aaf41

2 files changed

Lines changed: 38 additions & 17 deletions

File tree

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

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

5656
ip = ip.trim();
5757

58-
if (IPValidator.isIP(ip)) {
59-
return ip;
60-
}
61-
62-
if (ip.startsWith("[") && ip.endsWith("]")) {
63-
String unwrappedIp = ip.substring(1, ip.length() - 1);
64-
if (IPValidator.isIP(unwrappedIp)) {
65-
return unwrappedIp;
66-
}
67-
}
68-
58+
// Bracketed IPv6, optionally with a port: [ipv6] or [ipv6]:port.
59+
// Handle first so we never run the (expensive) full isIP on the bracketed form.
6960
if (ip.startsWith("[")) {
70-
int closingBracket = ip.indexOf("]:");
61+
int closingBracket = ip.indexOf(']');
7162
if (closingBracket > 0) {
7263
String unwrappedIp = ip.substring(1, closingBracket);
7364
if (IPValidator.isIP(unwrappedIp)) {
7465
return unwrappedIp;
7566
}
7667
}
68+
return null;
7769
}
7870

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];
71+
// IPv4 with a single port (a.b.c.d:port): handle before the version-less
72+
// isIP() so an IPv4:port value doesn't needlessly run the IPv6 regex.
73+
int firstColon = ip.indexOf(':');
74+
if (firstColon > 0 && ip.indexOf(':', firstColon + 1) < 0) {
75+
String host = ip.substring(0, firstColon);
76+
if (IPValidator.isIP(host, "4")) {
77+
return host;
8478
}
8579
}
8680

81+
// Plain IPv4 or IPv6 (no brackets, no port).
82+
if (IPValidator.isIP(ip)) {
83+
return ip;
84+
}
85+
8786
return null;
8887
}
8988

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

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,4 +214,26 @@ void testGetIpFromRequest_RawIpWithEmptyStringFallsBackToRawIp() {
214214
String result = getIpFromRequest("", new HashMap<>());
215215
assertEquals("", result);
216216
}
217+
218+
@Test
219+
void testGetIpFromRequest_IPv4WithMultiplePortColonsFallsBackToRawIp() {
220+
// More than one colon is not a valid IPv4:port, so it must not resolve.
221+
headers.put("X-Forwarded-For", List.of("1.2.3.4:80:90"));
222+
String result = getIpFromRequest("10.0.0.1", headers);
223+
assertEquals("10.0.0.1", result);
224+
}
225+
226+
@Test
227+
void testGetIpFromRequest_IPv4PortResolvesWithoutTouchingLaterEntries() {
228+
// First entry is a valid IPv4:port and must be returned as-is.
229+
headers.put("X-Forwarded-For", List.of("203.0.113.7:54321, 8.8.8.8"));
230+
String result = getIpFromRequest("10.0.0.1", headers);
231+
assertEquals("203.0.113.7", result);
232+
}
233+
234+
@Test
235+
void testGetIpFromRequest_BracketedIPv4Resolves() {
236+
String result = getIpFromRequest("[1.2.3.4]", new HashMap<>());
237+
assertEquals("1.2.3.4", result);
238+
}
217239
}

0 commit comments

Comments
 (0)