From 9c23def4bff310d0ab296fe84d2c1e248aab44cd Mon Sep 17 00:00:00 2001 From: "alejandro.gonzalez" Date: Tue, 5 Aug 2025 12:50:30 +0200 Subject: [PATCH 1/5] wip --- .../http/ClientIpAddressResolver.java | 164 ++++++++++-------- ...lientIpAddressResolverSpecification.groovy | 11 ++ 2 files changed, 99 insertions(+), 76 deletions(-) diff --git a/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/decorator/http/ClientIpAddressResolver.java b/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/decorator/http/ClientIpAddressResolver.java index c78ece62197..c48d40f0eea 100644 --- a/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/decorator/http/ClientIpAddressResolver.java +++ b/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/decorator/http/ClientIpAddressResolver.java @@ -100,6 +100,14 @@ private static InetAddress doResolve(AgentSpanContext.Extracted context, Mutable result = coalesce(result, addr); } + addr = tryHeader(context.getForwarded(), FORWARDED_PARSER); + if (addr != null) { + if (!isIpAddrPrivate(addr)) { + return addr; + } + result = coalesce(result, addr); + } + addr = tryHeader(context.getXClusterClientIp(), PLAIN_IP_ADDRESS_PARSER); if (addr != null) { if (!isIpAddrPrivate(addr)) { @@ -196,95 +204,99 @@ enum ForwardedParseState { @Override public InetAddress apply(String headerValue) { - InetAddress resultPrivate = null; - ForwardedParseState state = ForwardedParseState.BETWEEN; - - // https://datatracker.ietf.org/doc/html/rfc7239#section-4 - int pos = 0; - int end = headerValue.length(); - // compiler requires that these two be initialized: - int start = 0; - boolean considerValue = false; - while (pos < end) { - char c = headerValue.charAt(pos); - switch (state) { - case BETWEEN: - if (c == ' ' || c == ';' || c == ',') { - break; - } - start = pos; - state = ForwardedParseState.KEY; - break; - case KEY: - if (c != '=') { + String[] entries = headerValue.split(","); + for (int i = entries.length - 1; i >= 0; i--) { + String entry = entries[i].trim(); + InetAddress resultPrivate = null; + ForwardedParseState state = ForwardedParseState.BETWEEN; + // https://datatracker.ietf.org/doc/html/rfc7239#section-4 + int pos = 0; + int end = entry.length(); + // compiler requires that these two be initialized: + int start = 0; + boolean considerValue = false; + while (pos < end) { + char c = entry.charAt(pos); + switch (state) { + case BETWEEN: + if (c == ' ' || c == ';' || c == ',') { + break; + } + start = pos; + state = ForwardedParseState.KEY; break; - } + case KEY: + if (c != '=') { + break; + } - state = ForwardedParseState.BEFORE_VALUE; - if (pos - start == 3) { - String key = headerValue.substring(start, pos); - considerValue = key.equalsIgnoreCase("for"); - } else { - considerValue = false; - } - break; - case BEFORE_VALUE: - if (c == '"') { - start = pos + 1; - state = ForwardedParseState.VALUE_QUOTED; - } else if (c == ' ' || c == ';' || c == ',') { - // empty value - state = ForwardedParseState.BETWEEN; - } else { - start = pos; - state = ForwardedParseState.VALUE_TOKEN; - } - break; - case VALUE_TOKEN: - { - int tokenEnd; - if (c == ' ' || c == ';' || c == ',') { - tokenEnd = pos; - } else if (pos + 1 == end) { - tokenEnd = end; + state = ForwardedParseState.BEFORE_VALUE; + if (pos - start == 3) { + String key = entry.substring(start, pos); + considerValue = key.equalsIgnoreCase("for"); } else { - break; + considerValue = false; + } + break; + case BEFORE_VALUE: + if (c == '"') { + start = pos + 1; + state = ForwardedParseState.VALUE_QUOTED; + } else if (c == ' ' || c == ';' || c == ',') { + // empty value + state = ForwardedParseState.BETWEEN; + } else { + start = pos; + state = ForwardedParseState.VALUE_TOKEN; } + break; + case VALUE_TOKEN: + { + int tokenEnd; + if (c == ' ' || c == ';' || c == ',') { + tokenEnd = pos; + } else if (pos + 1 == end) { + tokenEnd = end; + } else { + break; + } - if (considerValue) { - InetAddress ipAddr = - parseIpAddressAndMaybePort(headerValue.substring(start, tokenEnd)); - if (ipAddr != null) { - if (isIpAddrPrivate(ipAddr)) { - if (resultPrivate == null) { - resultPrivate = ipAddr; + if (considerValue) { + InetAddress ipAddr = parseIpAddressAndMaybePort(entry.substring(start, tokenEnd)); + if (ipAddr != null) { + if (isIpAddrPrivate(ipAddr)) { + if (resultPrivate == null) { + resultPrivate = ipAddr; + } + } else { + return ipAddr; } - } else { - return ipAddr; } } + state = ForwardedParseState.BETWEEN; + break; } - state = ForwardedParseState.BETWEEN; - break; - } - case VALUE_QUOTED: - if (c == '"') { - if (considerValue) { - InetAddress ipAddr = parseIpAddressAndMaybePort(headerValue.substring(start, pos)); - if (ipAddr != null && !isIpAddrPrivate(ipAddr)) { - return ipAddr; + case VALUE_QUOTED: + if (c == '"') { + if (considerValue) { + InetAddress ipAddr = parseIpAddressAndMaybePort(entry.substring(start, pos)); + if (ipAddr != null && !isIpAddrPrivate(ipAddr)) { + return ipAddr; + } } + state = ForwardedParseState.BETWEEN; + } else if (c == '\\') { + pos++; } - state = ForwardedParseState.BETWEEN; - } else if (c == '\\') { - pos++; - } - break; + break; + } + pos++; + } + if (resultPrivate != null) { + return resultPrivate; } - pos++; } - - return resultPrivate; + return null; } } diff --git a/dd-java-agent/agent-bootstrap/src/test/groovy/datadog/trace/bootstrap/instrumentation/decorator/http/ClientIpAddressResolverSpecification.groovy b/dd-java-agent/agent-bootstrap/src/test/groovy/datadog/trace/bootstrap/instrumentation/decorator/http/ClientIpAddressResolverSpecification.groovy index 29e12001bc4..9f4b7b0eb44 100644 --- a/dd-java-agent/agent-bootstrap/src/test/groovy/datadog/trace/bootstrap/instrumentation/decorator/http/ClientIpAddressResolverSpecification.groovy +++ b/dd-java-agent/agent-bootstrap/src/test/groovy/datadog/trace/bootstrap/instrumentation/decorator/http/ClientIpAddressResolverSpecification.groovy @@ -70,6 +70,14 @@ class ClientIpAddressResolverSpecification extends Specification { 'fastly-client-ip' | '3.3.3.3' | '3.3.3.3' 'cf-connecting-ip' | '4.4.4.4' | '4.4.4.4' 'cf-connecting-ipv6' | '2001::2' | '2001::2' + + 'forwarded' | 'for=192.0.2.60;proto=http;by=203.0.113.43' | '192.0.2.60' + 'forwarded' | 'For="[2001:db8:cafe::17]:4711"' | '2001:db8:cafe::17' + 'forwarded' | 'for=192.0.2.43, for=198.51.100.17' | '198.51.100.17' + 'forwarded' | 'for=192.0.2.43;proto=https;by=203.0.113.43' | '192.0.2.43' + 'forwarded' | 'for="_gazonk"' | null + 'forwarded' | 'for=unknown, for=8.8.8.8' | '8.8.8.8' + 'forwarded' | 'for="[::ffff:192.0.2.128]";proto=http' | '192.0.2.128' } void 'test recognition strategy with custom header'() { @@ -113,6 +121,9 @@ class ClientIpAddressResolverSpecification extends Specification { then: 1 * context.getForwardedFor() >> null + then: + 1 * context.getForwarded() >> null + then: 1 * context.getXClusterClientIp() >> null From 35981153c1ed1b7ca2a0be5dd5e0f44f50c6e924 Mon Sep 17 00:00:00 2001 From: "alejandro.gonzalez" Date: Tue, 5 Aug 2025 13:19:57 +0200 Subject: [PATCH 2/5] wip --- .../http/ClientIpAddressResolver.java | 167 +++++++++--------- 1 file changed, 88 insertions(+), 79 deletions(-) diff --git a/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/decorator/http/ClientIpAddressResolver.java b/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/decorator/http/ClientIpAddressResolver.java index c48d40f0eea..c3f0f2336f8 100644 --- a/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/decorator/http/ClientIpAddressResolver.java +++ b/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/decorator/http/ClientIpAddressResolver.java @@ -204,96 +204,105 @@ enum ForwardedParseState { @Override public InetAddress apply(String headerValue) { - String[] entries = headerValue.split(","); - for (int i = entries.length - 1; i >= 0; i--) { - String entry = entries[i].trim(); - InetAddress resultPrivate = null; - ForwardedParseState state = ForwardedParseState.BETWEEN; - // https://datatracker.ietf.org/doc/html/rfc7239#section-4 - int pos = 0; - int end = entry.length(); - // compiler requires that these two be initialized: - int start = 0; - boolean considerValue = false; - while (pos < end) { - char c = entry.charAt(pos); - switch (state) { - case BETWEEN: - if (c == ' ' || c == ';' || c == ',') { + int end = headerValue.length(); + int entryEnd = end; + // Parse entries right-to-left, separated by ',' + for (int i = end - 1; i >= -1; i--) { + if (i == -1 || headerValue.charAt(i) == ',') { + int entryStart = i + 1; + // skip leading spaces + while (entryStart < entryEnd && headerValue.charAt(entryStart) == ' ') { + entryStart++; + } + String entry = headerValue.substring(entryStart, entryEnd); + InetAddress resultPrivate = null; + ForwardedParseState state = ForwardedParseState.BETWEEN; + // https://datatracker.ietf.org/doc/html/rfc7239#section-4 + int pos = 0; + int entryLen = entry.length(); + // compiler requires that these two be initialized: + int start = 0; + boolean considerValue = false; + while (pos < entryLen) { + char c = entry.charAt(pos); + switch (state) { + case BETWEEN: + if (c == ' ' || c == ';' || c == ',') { + break; + } + start = pos; + state = ForwardedParseState.KEY; break; - } - start = pos; - state = ForwardedParseState.KEY; - break; - case KEY: - if (c != '=') { + case KEY: + if (c != '=') { + break; + } + state = ForwardedParseState.BEFORE_VALUE; + if (pos - start == 3) { + String key = entry.substring(start, pos); + considerValue = key.equalsIgnoreCase("for"); + } else { + considerValue = false; + } break; - } - - state = ForwardedParseState.BEFORE_VALUE; - if (pos - start == 3) { - String key = entry.substring(start, pos); - considerValue = key.equalsIgnoreCase("for"); - } else { - considerValue = false; - } - break; - case BEFORE_VALUE: - if (c == '"') { - start = pos + 1; - state = ForwardedParseState.VALUE_QUOTED; - } else if (c == ' ' || c == ';' || c == ',') { - // empty value - state = ForwardedParseState.BETWEEN; - } else { - start = pos; - state = ForwardedParseState.VALUE_TOKEN; - } - break; - case VALUE_TOKEN: - { - int tokenEnd; - if (c == ' ' || c == ';' || c == ',') { - tokenEnd = pos; - } else if (pos + 1 == end) { - tokenEnd = end; + case BEFORE_VALUE: + if (c == '"') { + start = pos + 1; + state = ForwardedParseState.VALUE_QUOTED; + } else if (c == ' ' || c == ';' || c == ',') { + // empty value + state = ForwardedParseState.BETWEEN; } else { - break; + start = pos; + state = ForwardedParseState.VALUE_TOKEN; } - - if (considerValue) { - InetAddress ipAddr = parseIpAddressAndMaybePort(entry.substring(start, tokenEnd)); - if (ipAddr != null) { - if (isIpAddrPrivate(ipAddr)) { - if (resultPrivate == null) { - resultPrivate = ipAddr; + break; + case VALUE_TOKEN: + { + int tokenEnd; + if (c == ' ' || c == ';' || c == ',') { + tokenEnd = pos; + } else if (pos + 1 == entryLen) { + tokenEnd = entryLen; + } else { + break; + } + if (considerValue) { + InetAddress ipAddr = + parseIpAddressAndMaybePort(entry.substring(start, tokenEnd)); + if (ipAddr != null) { + if (isIpAddrPrivate(ipAddr)) { + if (resultPrivate == null) { + resultPrivate = ipAddr; + } + } else { + return ipAddr; } - } else { - return ipAddr; } } + state = ForwardedParseState.BETWEEN; + break; } - state = ForwardedParseState.BETWEEN; - break; - } - case VALUE_QUOTED: - if (c == '"') { - if (considerValue) { - InetAddress ipAddr = parseIpAddressAndMaybePort(entry.substring(start, pos)); - if (ipAddr != null && !isIpAddrPrivate(ipAddr)) { - return ipAddr; + case VALUE_QUOTED: + if (c == '"') { + if (considerValue) { + InetAddress ipAddr = parseIpAddressAndMaybePort(entry.substring(start, pos)); + if (ipAddr != null && !isIpAddrPrivate(ipAddr)) { + return ipAddr; + } } + state = ForwardedParseState.BETWEEN; + } else if (c == '\\') { + pos++; } - state = ForwardedParseState.BETWEEN; - } else if (c == '\\') { - pos++; - } - break; + break; + } + pos++; } - pos++; - } - if (resultPrivate != null) { - return resultPrivate; + if (resultPrivate != null) { + return resultPrivate; + } + entryEnd = i; } } return null; From 5f8c944f5be91af261012b7c2f81737220a2e01d Mon Sep 17 00:00:00 2001 From: "alejandro.gonzalez" Date: Wed, 6 Aug 2025 11:42:14 +0200 Subject: [PATCH 3/5] wip --- .../http/ClientIpAddressResolverSpecification.groovy | 10 ++++++++++ .../trace/core/propagation/B3HttpExtractorTest.groovy | 2 ++ .../core/propagation/DatadogHttpExtractorTest.groovy | 2 ++ .../core/propagation/HaystackHttpExtractorTest.groovy | 2 ++ .../core/propagation/NoneHttpExtractorTest.groovy | 2 ++ .../trace/core/propagation/W3CHttpExtractorTest.groovy | 2 ++ .../core/propagation/XRayHttpExtractorTest.groovy | 2 ++ 7 files changed, 22 insertions(+) diff --git a/dd-java-agent/agent-bootstrap/src/test/groovy/datadog/trace/bootstrap/instrumentation/decorator/http/ClientIpAddressResolverSpecification.groovy b/dd-java-agent/agent-bootstrap/src/test/groovy/datadog/trace/bootstrap/instrumentation/decorator/http/ClientIpAddressResolverSpecification.groovy index 9f4b7b0eb44..ba311fe5426 100644 --- a/dd-java-agent/agent-bootstrap/src/test/groovy/datadog/trace/bootstrap/instrumentation/decorator/http/ClientIpAddressResolverSpecification.groovy +++ b/dd-java-agent/agent-bootstrap/src/test/groovy/datadog/trace/bootstrap/instrumentation/decorator/http/ClientIpAddressResolverSpecification.groovy @@ -71,6 +71,15 @@ class ClientIpAddressResolverSpecification extends Specification { 'cf-connecting-ip' | '4.4.4.4' | '4.4.4.4' 'cf-connecting-ipv6' | '2001::2' | '2001::2' + 'forwarded' | 'for="[2001::1]:1111"' | '2001::1' + 'forwarded' | 'fOr="[2001::1]:1111"' | '2001::1' + 'forwarded' | 'for=some_host' | null + 'forwarded' | 'for=127.0.0.1, FOR=1.1.1.1' | '1.1.1.1' + 'forwarded' |'for="\"foobar";proto=http,FOR="1.1.1.1"' | '1.1.1.1' + 'forwarded' | 'for="8.8.8.8:2222",' | '8.8.8.8' + 'forwarded' | 'for="8.8.8.8' | null // quote not closed + 'forwarded' | 'far="8.8.8.8",for=4.4.4.4;' | '4.4.4.4' + 'forwarded' | ' for=127.0.0.1,for= for=,for=;"for = for="" ,; for=8.8.8.8;' | '8.8.8.8' 'forwarded' | 'for=192.0.2.60;proto=http;by=203.0.113.43' | '192.0.2.60' 'forwarded' | 'For="[2001:db8:cafe::17]:4711"' | '2001:db8:cafe::17' 'forwarded' | 'for=192.0.2.43, for=198.51.100.17' | '198.51.100.17' @@ -173,6 +182,7 @@ class ClientIpAddressResolverSpecification extends Specification { 1 * context.getXForwardedFor() >> '127.0.0.1' 1 * context.getXRealIp() >> '127.0.0.2' 1 * context.getXClientIp() >> '127.0.0.3' + 1 * context.getForwarded() >> 'for=127.0.0.4' 1 * context.getXClusterClientIp() >> '127.0.0.5' 1 * context.getForwardedFor() >> '127.0.0.6' 1 * context.getTrueClientIp() >> '127.0.0.9' diff --git a/dd-trace-core/src/test/groovy/datadog/trace/core/propagation/B3HttpExtractorTest.groovy b/dd-trace-core/src/test/groovy/datadog/trace/core/propagation/B3HttpExtractorTest.groovy index 513628c9498..ce4eb188e5b 100644 --- a/dd-trace-core/src/test/groovy/datadog/trace/core/propagation/B3HttpExtractorTest.groovy +++ b/dd-trace-core/src/test/groovy/datadog/trace/core/propagation/B3HttpExtractorTest.groovy @@ -367,6 +367,7 @@ class B3HttpExtractorTest extends DDSpecification { (HttpCodec.X_CLIENT_IP_KEY): '3.3.3.3', (HttpCodec.TRUE_CLIENT_IP_KEY): '4.4.4.4', (HttpCodec.FORWARDED_FOR_KEY): '5.5.5.5', + (HttpCodec.FORWARDED_KEY): '6.6.6.6', (HttpCodec.FASTLY_CLIENT_IP_KEY): '7.7.7.7', (HttpCodec.CF_CONNECTING_IP_KEY): '8.8.8.8', (HttpCodec.CF_CONNECTING_IP_V6_KEY): '9.9.9.9', @@ -382,6 +383,7 @@ class B3HttpExtractorTest extends DDSpecification { assert context.XClientIp == '3.3.3.3' assert context.trueClientIp == '4.4.4.4' assert context.forwardedFor == '5.5.5.5' + assert context.forwarded == '6.6.6.6' assert context.fastlyClientIp == '7.7.7.7' assert context.cfConnectingIp == '8.8.8.8' assert context.cfConnectingIpv6 == '9.9.9.9' diff --git a/dd-trace-core/src/test/groovy/datadog/trace/core/propagation/DatadogHttpExtractorTest.groovy b/dd-trace-core/src/test/groovy/datadog/trace/core/propagation/DatadogHttpExtractorTest.groovy index c8d7c905694..4541d264cfa 100644 --- a/dd-trace-core/src/test/groovy/datadog/trace/core/propagation/DatadogHttpExtractorTest.groovy +++ b/dd-trace-core/src/test/groovy/datadog/trace/core/propagation/DatadogHttpExtractorTest.groovy @@ -438,6 +438,7 @@ class DatadogHttpExtractorTest extends DDSpecification { (HttpCodec.X_CLIENT_IP_KEY): '3.3.3.3', (HttpCodec.TRUE_CLIENT_IP_KEY): '4.4.4.4', (HttpCodec.FORWARDED_FOR_KEY): '5.5.5.5', + (HttpCodec.FORWARDED_KEY): '6.6.6.6', (HttpCodec.FASTLY_CLIENT_IP_KEY): '7.7.7.7', (HttpCodec.CF_CONNECTING_IP_KEY): '8.8.8.8', (HttpCodec.CF_CONNECTING_IP_V6_KEY): '9.9.9.9', @@ -453,6 +454,7 @@ class DatadogHttpExtractorTest extends DDSpecification { assert context.XClientIp == '3.3.3.3' assert context.trueClientIp == '4.4.4.4' assert context.forwardedFor == '5.5.5.5' + assert context.forwarded == '6.6.6.6' assert context.fastlyClientIp == '7.7.7.7' assert context.cfConnectingIp == '8.8.8.8' assert context.cfConnectingIpv6 == '9.9.9.9' diff --git a/dd-trace-core/src/test/groovy/datadog/trace/core/propagation/HaystackHttpExtractorTest.groovy b/dd-trace-core/src/test/groovy/datadog/trace/core/propagation/HaystackHttpExtractorTest.groovy index 06d3fe492db..b3b7080cf14 100644 --- a/dd-trace-core/src/test/groovy/datadog/trace/core/propagation/HaystackHttpExtractorTest.groovy +++ b/dd-trace-core/src/test/groovy/datadog/trace/core/propagation/HaystackHttpExtractorTest.groovy @@ -300,6 +300,7 @@ class HaystackHttpExtractorTest extends DDSpecification { (HttpCodec.X_CLIENT_IP_KEY): '3.3.3.3', (HttpCodec.TRUE_CLIENT_IP_KEY): '4.4.4.4', (HttpCodec.FORWARDED_FOR_KEY): '5.5.5.5', + (HttpCodec.FORWARDED_KEY): '6.6.6.6', (HttpCodec.FASTLY_CLIENT_IP_KEY): '7.7.7.7', (HttpCodec.CF_CONNECTING_IP_KEY): '8.8.8.8', (HttpCodec.CF_CONNECTING_IP_V6_KEY): '9.9.9.9', @@ -315,6 +316,7 @@ class HaystackHttpExtractorTest extends DDSpecification { assert context.XClientIp == '3.3.3.3' assert context.trueClientIp == '4.4.4.4' assert context.forwardedFor == '5.5.5.5' + assert context.forwarded == '6.6.6.6' assert context.fastlyClientIp == '7.7.7.7' assert context.cfConnectingIp == '8.8.8.8' assert context.cfConnectingIpv6 == '9.9.9.9' diff --git a/dd-trace-core/src/test/groovy/datadog/trace/core/propagation/NoneHttpExtractorTest.groovy b/dd-trace-core/src/test/groovy/datadog/trace/core/propagation/NoneHttpExtractorTest.groovy index 516fa007964..29295b3b584 100644 --- a/dd-trace-core/src/test/groovy/datadog/trace/core/propagation/NoneHttpExtractorTest.groovy +++ b/dd-trace-core/src/test/groovy/datadog/trace/core/propagation/NoneHttpExtractorTest.groovy @@ -303,6 +303,7 @@ class NoneHttpExtractorTest extends DDSpecification { (HttpCodec.X_CLIENT_IP_KEY): '3.3.3.3', (HttpCodec.TRUE_CLIENT_IP_KEY): '4.4.4.4', (HttpCodec.FORWARDED_FOR_KEY): '5.5.5.5', + (HttpCodec.FORWARDED_KEY): '6.6.6.6', (HttpCodec.FASTLY_CLIENT_IP_KEY): '7.7.7.7', (HttpCodec.CF_CONNECTING_IP_KEY): '8.8.8.8', (HttpCodec.CF_CONNECTING_IP_V6_KEY): '9.9.9.9', @@ -318,6 +319,7 @@ class NoneHttpExtractorTest extends DDSpecification { assert context.XClientIp == '3.3.3.3' assert context.trueClientIp == '4.4.4.4' assert context.forwardedFor == '5.5.5.5' + assert context.forwarded == '6.6.6.6' assert context.fastlyClientIp == '7.7.7.7' assert context.cfConnectingIp == '8.8.8.8' assert context.cfConnectingIpv6 == '9.9.9.9' diff --git a/dd-trace-core/src/test/groovy/datadog/trace/core/propagation/W3CHttpExtractorTest.groovy b/dd-trace-core/src/test/groovy/datadog/trace/core/propagation/W3CHttpExtractorTest.groovy index d82b9b56fca..a0c7e7ee01b 100644 --- a/dd-trace-core/src/test/groovy/datadog/trace/core/propagation/W3CHttpExtractorTest.groovy +++ b/dd-trace-core/src/test/groovy/datadog/trace/core/propagation/W3CHttpExtractorTest.groovy @@ -365,6 +365,7 @@ class W3CHttpExtractorTest extends DDSpecification { (HttpCodec.X_CLIENT_IP_KEY): '3.3.3.3', (HttpCodec.TRUE_CLIENT_IP_KEY): '4.4.4.4', (HttpCodec.FORWARDED_FOR_KEY): '5.5.5.5', + (HttpCodec.FORWARDED_KEY): '6.6.6.6', (HttpCodec.FASTLY_CLIENT_IP_KEY): '7.7.7.7', (HttpCodec.CF_CONNECTING_IP_KEY): '8.8.8.8', (HttpCodec.CF_CONNECTING_IP_V6_KEY): '9.9.9.9', @@ -380,6 +381,7 @@ class W3CHttpExtractorTest extends DDSpecification { assert context.XClientIp == '3.3.3.3' assert context.trueClientIp == '4.4.4.4' assert context.forwardedFor == '5.5.5.5' + assert context.forwarded == '6.6.6.6' assert context.fastlyClientIp == '7.7.7.7' assert context.cfConnectingIp == '8.8.8.8' assert context.cfConnectingIpv6 == '9.9.9.9' diff --git a/dd-trace-core/src/test/groovy/datadog/trace/core/propagation/XRayHttpExtractorTest.groovy b/dd-trace-core/src/test/groovy/datadog/trace/core/propagation/XRayHttpExtractorTest.groovy index b45c655cae6..78b6d5c60d5 100644 --- a/dd-trace-core/src/test/groovy/datadog/trace/core/propagation/XRayHttpExtractorTest.groovy +++ b/dd-trace-core/src/test/groovy/datadog/trace/core/propagation/XRayHttpExtractorTest.groovy @@ -256,6 +256,7 @@ class XRayHttpExtractorTest extends DDSpecification { (HttpCodec.X_CLIENT_IP_KEY): '3.3.3.3', (HttpCodec.TRUE_CLIENT_IP_KEY): '4.4.4.4', (HttpCodec.FORWARDED_FOR_KEY): '5.5.5.5', + (HttpCodec.FORWARDED_KEY): '6.6.6.6', (HttpCodec.FASTLY_CLIENT_IP_KEY): '7.7.7.7', (HttpCodec.CF_CONNECTING_IP_KEY): '8.8.8.8', (HttpCodec.CF_CONNECTING_IP_V6_KEY): '9.9.9.9', @@ -271,6 +272,7 @@ class XRayHttpExtractorTest extends DDSpecification { assert context.XClientIp == '3.3.3.3' assert context.trueClientIp == '4.4.4.4' assert context.forwardedFor == '5.5.5.5' + assert context.forwarded == '6.6.6.6' assert context.fastlyClientIp == '7.7.7.7' assert context.cfConnectingIp == '8.8.8.8' assert context.cfConnectingIpv6 == '9.9.9.9' From 6af0ef3334f7768b57773d49cc61d0e54d562f0c Mon Sep 17 00:00:00 2001 From: "alejandro.gonzalez" Date: Wed, 6 Aug 2025 12:00:02 +0200 Subject: [PATCH 4/5] wip --- .../http/ClientIpAddressResolver.java | 173 ++++++++---------- ...lientIpAddressResolverSpecification.groovy | 1 - 2 files changed, 80 insertions(+), 94 deletions(-) diff --git a/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/decorator/http/ClientIpAddressResolver.java b/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/decorator/http/ClientIpAddressResolver.java index c3f0f2336f8..49e54ec74c1 100644 --- a/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/decorator/http/ClientIpAddressResolver.java +++ b/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/decorator/http/ClientIpAddressResolver.java @@ -204,108 +204,95 @@ enum ForwardedParseState { @Override public InetAddress apply(String headerValue) { + InetAddress resultPrivate = null; + ForwardedParseState state = ForwardedParseState.BETWEEN; + + // https://datatracker.ietf.org/doc/html/rfc7239#section-4 + int pos = 0; int end = headerValue.length(); - int entryEnd = end; - // Parse entries right-to-left, separated by ',' - for (int i = end - 1; i >= -1; i--) { - if (i == -1 || headerValue.charAt(i) == ',') { - int entryStart = i + 1; - // skip leading spaces - while (entryStart < entryEnd && headerValue.charAt(entryStart) == ' ') { - entryStart++; - } - String entry = headerValue.substring(entryStart, entryEnd); - InetAddress resultPrivate = null; - ForwardedParseState state = ForwardedParseState.BETWEEN; - // https://datatracker.ietf.org/doc/html/rfc7239#section-4 - int pos = 0; - int entryLen = entry.length(); - // compiler requires that these two be initialized: - int start = 0; - boolean considerValue = false; - while (pos < entryLen) { - char c = entry.charAt(pos); - switch (state) { - case BETWEEN: - if (c == ' ' || c == ';' || c == ',') { - break; - } - start = pos; - state = ForwardedParseState.KEY; - break; - case KEY: - if (c != '=') { - break; - } - state = ForwardedParseState.BEFORE_VALUE; - if (pos - start == 3) { - String key = entry.substring(start, pos); - considerValue = key.equalsIgnoreCase("for"); - } else { - considerValue = false; - } - break; - case BEFORE_VALUE: - if (c == '"') { - start = pos + 1; - state = ForwardedParseState.VALUE_QUOTED; - } else if (c == ' ' || c == ';' || c == ',') { - // empty value - state = ForwardedParseState.BETWEEN; - } else { - start = pos; - state = ForwardedParseState.VALUE_TOKEN; - } + // compiler requires that these two be initialized: + int start = 0; + boolean considerValue = false; + while (pos < end) { + char c = headerValue.charAt(pos); + switch (state) { + case BETWEEN: + if (c == ' ' || c == ';' || c == ',') { + break; + } + start = pos; + state = ForwardedParseState.KEY; + break; + case KEY: + if (c != '=') { + break; + } + + state = ForwardedParseState.BEFORE_VALUE; + if (pos - start == 3) { + String key = headerValue.substring(start, pos); + considerValue = key.equalsIgnoreCase("for"); + } else { + considerValue = false; + } + break; + case BEFORE_VALUE: + if (c == '"') { + start = pos + 1; + state = ForwardedParseState.VALUE_QUOTED; + } else if (c == ' ' || c == ';' || c == ',') { + // empty value + state = ForwardedParseState.BETWEEN; + } else { + start = pos; + state = ForwardedParseState.VALUE_TOKEN; + } + break; + case VALUE_TOKEN: + { + int tokenEnd; + if (c == ' ' || c == ';' || c == ',') { + tokenEnd = pos; + } else if (pos + 1 == end) { + tokenEnd = end; + } else { break; - case VALUE_TOKEN: - { - int tokenEnd; - if (c == ' ' || c == ';' || c == ',') { - tokenEnd = pos; - } else if (pos + 1 == entryLen) { - tokenEnd = entryLen; - } else { - break; - } - if (considerValue) { - InetAddress ipAddr = - parseIpAddressAndMaybePort(entry.substring(start, tokenEnd)); - if (ipAddr != null) { - if (isIpAddrPrivate(ipAddr)) { - if (resultPrivate == null) { - resultPrivate = ipAddr; - } - } else { - return ipAddr; - } + } + + if (considerValue) { + InetAddress ipAddr = + parseIpAddressAndMaybePort(headerValue.substring(start, tokenEnd)); + if (ipAddr != null) { + if (isIpAddrPrivate(ipAddr)) { + if (resultPrivate == null) { + resultPrivate = ipAddr; } + } else { + return ipAddr; } - state = ForwardedParseState.BETWEEN; - break; } - case VALUE_QUOTED: - if (c == '"') { - if (considerValue) { - InetAddress ipAddr = parseIpAddressAndMaybePort(entry.substring(start, pos)); - if (ipAddr != null && !isIpAddrPrivate(ipAddr)) { - return ipAddr; - } - } - state = ForwardedParseState.BETWEEN; - } else if (c == '\\') { - pos++; + } + state = ForwardedParseState.BETWEEN; + break; + } + case VALUE_QUOTED: + if (c == '"') { + if (considerValue) { + InetAddress ipAddr = parseIpAddressAndMaybePort(headerValue.substring(start, pos)); + if (ipAddr != null && !isIpAddrPrivate(ipAddr)) { + return ipAddr; } - break; + } + state = ForwardedParseState.BETWEEN; + } else if (c == '\\') { + pos++; } - pos++; - } - if (resultPrivate != null) { - return resultPrivate; - } - entryEnd = i; + break; } + pos++; } - return null; + + return resultPrivate; } } diff --git a/dd-java-agent/agent-bootstrap/src/test/groovy/datadog/trace/bootstrap/instrumentation/decorator/http/ClientIpAddressResolverSpecification.groovy b/dd-java-agent/agent-bootstrap/src/test/groovy/datadog/trace/bootstrap/instrumentation/decorator/http/ClientIpAddressResolverSpecification.groovy index ba311fe5426..04546586e34 100644 --- a/dd-java-agent/agent-bootstrap/src/test/groovy/datadog/trace/bootstrap/instrumentation/decorator/http/ClientIpAddressResolverSpecification.groovy +++ b/dd-java-agent/agent-bootstrap/src/test/groovy/datadog/trace/bootstrap/instrumentation/decorator/http/ClientIpAddressResolverSpecification.groovy @@ -82,7 +82,6 @@ class ClientIpAddressResolverSpecification extends Specification { 'forwarded' | ' for=127.0.0.1,for= for=,for=;"for = for="" ,; for=8.8.8.8;' | '8.8.8.8' 'forwarded' | 'for=192.0.2.60;proto=http;by=203.0.113.43' | '192.0.2.60' 'forwarded' | 'For="[2001:db8:cafe::17]:4711"' | '2001:db8:cafe::17' - 'forwarded' | 'for=192.0.2.43, for=198.51.100.17' | '198.51.100.17' 'forwarded' | 'for=192.0.2.43;proto=https;by=203.0.113.43' | '192.0.2.43' 'forwarded' | 'for="_gazonk"' | null 'forwarded' | 'for=unknown, for=8.8.8.8' | '8.8.8.8' From 397b6e8738ca8a8d1d377a033c81ddc1087b0f2e Mon Sep 17 00:00:00 2001 From: "alejandro.gonzalez" Date: Wed, 6 Aug 2025 12:46:22 +0200 Subject: [PATCH 5/5] wip --- .../decorator/http/ClientIpAddressResolver.java | 4 ++-- .../http/ClientIpAddressResolverSpecification.groovy | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/decorator/http/ClientIpAddressResolver.java b/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/decorator/http/ClientIpAddressResolver.java index 49e54ec74c1..6a4535ba9ac 100644 --- a/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/decorator/http/ClientIpAddressResolver.java +++ b/dd-java-agent/agent-bootstrap/src/main/java/datadog/trace/bootstrap/instrumentation/decorator/http/ClientIpAddressResolver.java @@ -92,7 +92,7 @@ private static InetAddress doResolve(AgentSpanContext.Extracted context, Mutable result = coalesce(result, addr); } - addr = tryHeader(context.getForwardedFor(), PLAIN_IP_ADDRESS_PARSER); + addr = tryHeader(context.getForwarded(), FORWARDED_PARSER); if (addr != null) { if (!isIpAddrPrivate(addr)) { return addr; @@ -100,7 +100,7 @@ private static InetAddress doResolve(AgentSpanContext.Extracted context, Mutable result = coalesce(result, addr); } - addr = tryHeader(context.getForwarded(), FORWARDED_PARSER); + addr = tryHeader(context.getForwardedFor(), PLAIN_IP_ADDRESS_PARSER); if (addr != null) { if (!isIpAddrPrivate(addr)) { return addr; diff --git a/dd-java-agent/agent-bootstrap/src/test/groovy/datadog/trace/bootstrap/instrumentation/decorator/http/ClientIpAddressResolverSpecification.groovy b/dd-java-agent/agent-bootstrap/src/test/groovy/datadog/trace/bootstrap/instrumentation/decorator/http/ClientIpAddressResolverSpecification.groovy index 04546586e34..fbc26ab4cd8 100644 --- a/dd-java-agent/agent-bootstrap/src/test/groovy/datadog/trace/bootstrap/instrumentation/decorator/http/ClientIpAddressResolverSpecification.groovy +++ b/dd-java-agent/agent-bootstrap/src/test/groovy/datadog/trace/bootstrap/instrumentation/decorator/http/ClientIpAddressResolverSpecification.groovy @@ -127,10 +127,10 @@ class ClientIpAddressResolverSpecification extends Specification { 1 * context.getXClientIp() >> null then: - 1 * context.getForwardedFor() >> null + 1 * context.getForwarded() >> null then: - 1 * context.getForwarded() >> null + 1 * context.getForwardedFor() >> null then: 1 * context.getXClusterClientIp() >> null