From d9d8576746016af1da59e55c61e1586fe19bc9f4 Mon Sep 17 00:00:00 2001 From: Gregory Oschwald Date: Fri, 10 Jul 2026 20:56:02 +0000 Subject: [PATCH] Add residential proxy data to Anonymizer record Adds a residential field to the Anonymizer record containing residential proxy data for the network. This data is exposed as a new reusable AnonymizerFeed record (confidence, networkLastSeen, providerName) so that future anonymizer feeds (e.g. VPN, mobile, datacenter) can share the same shape. The residential field may be populated even when none of the other Anonymizer fields are set. A deprecated constructor with the previous signature is retained on Anonymizer for backward compatibility in this minor release. Co-Authored-By: Claude Fable 5 --- CHANGELOG.md | 11 ++++ .../com/maxmind/geoip2/record/Anonymizer.java | 61 ++++++++++++++++++- .../maxmind/geoip2/record/AnonymizerFeed.java | 43 +++++++++++++ .../geoip2/model/InsightsResponseTest.java | 20 ++++++ .../com/maxmind/geoip2/model/JsonTest.java | 5 ++ src/test/resources/test-data/insights0.json | 7 ++- src/test/resources/test-data/insights1.json | 7 ++- 7 files changed, 150 insertions(+), 4 deletions(-) create mode 100644 src/main/java/com/maxmind/geoip2/record/AnonymizerFeed.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 0799b303..8da00550 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,17 @@ CHANGELOG ========= +5.2.0 (unreleased) +------------------ + +* A new `residential` field has been added to the `Anonymizer` record. This + is an `AnonymizerFeed` record containing `confidence`, `networkLastSeen`, + and `providerName` fields with residential proxy data for the network. + `residential` may be populated even when none of the other fields on + `Anonymizer` are set. The `AnonymizerFeed` record is intended to be reused + for additional feeds, such as VPNs, mobile networks, and hosting or + datacenter providers, that may be added in the future. + 5.1.0 (2026-05-12) ------------------ diff --git a/src/main/java/com/maxmind/geoip2/record/Anonymizer.java b/src/main/java/com/maxmind/geoip2/record/Anonymizer.java index 6c94527a..cbc56ea2 100644 --- a/src/main/java/com/maxmind/geoip2/record/Anonymizer.java +++ b/src/main/java/com/maxmind/geoip2/record/Anonymizer.java @@ -31,6 +31,9 @@ * @param providerName The name of the VPN provider (e.g., NordVPN, SurfShark, etc.) associated * with the network. This is only available from the GeoIP Insights * web service. + * @param residential Residential proxy data for the network. This may be populated even when + * none of the other fields on this record are set. This is only available + * from the GeoIP Insights web service. */ public record Anonymizer( @JsonProperty("confidence") @@ -58,14 +61,68 @@ public record Anonymizer( LocalDate networkLastSeen, @JsonProperty("provider_name") - String providerName + String providerName, + + @JsonProperty("residential") + AnonymizerFeed residential ) implements JsonSerializable { + /** + * Compact canonical constructor that sets a default for a null {@code residential} value. + */ + public Anonymizer { + residential = residential != null ? residential : new AnonymizerFeed(); + } + /** * Constructs an {@code Anonymizer} record with {@code null} values for all the nullable * fields and {@code false} for all boolean fields. */ public Anonymizer() { - this(null, false, false, false, false, false, false, null, null); + this(null, false, false, false, false, false, false, null, null, null); + } + + /** + * Constructs an {@code Anonymizer} record without the {@code residential} field. + * + * @param confidence the confidence that the network is an actively used VPN service + * @param isAnonymous whether the IP address belongs to any sort of anonymous network + * @param isAnonymousVpn whether the IP address is registered to an anonymous VPN provider + * @param isHostingProvider whether the IP address belongs to a hosting or VPN provider + * @param isPublicProxy whether the IP address belongs to a public proxy + * @param isResidentialProxy whether the IP address is on a suspected anonymizing network + * and belongs to a residential ISP + * @param isTorExitNode whether the IP address is a Tor exit node + * @param networkLastSeen the last day that the network was sighted in our analysis of + * anonymized networks + * @param providerName the name of the VPN provider associated with the network + * @deprecated Use the canonical constructor that also accepts the {@code residential} + * field. This constructor is provided for backward compatibility and will be + * removed in version 6.0.0. + */ + @Deprecated(since = "5.2.0", forRemoval = true) + public Anonymizer( + Integer confidence, + boolean isAnonymous, + boolean isAnonymousVpn, + boolean isHostingProvider, + boolean isPublicProxy, + boolean isResidentialProxy, + boolean isTorExitNode, + LocalDate networkLastSeen, + String providerName + ) { + this( + confidence, + isAnonymous, + isAnonymousVpn, + isHostingProvider, + isPublicProxy, + isResidentialProxy, + isTorExitNode, + networkLastSeen, + providerName, + null + ); } } diff --git a/src/main/java/com/maxmind/geoip2/record/AnonymizerFeed.java b/src/main/java/com/maxmind/geoip2/record/AnonymizerFeed.java new file mode 100644 index 00000000..cf5d5c26 --- /dev/null +++ b/src/main/java/com/maxmind/geoip2/record/AnonymizerFeed.java @@ -0,0 +1,43 @@ +package com.maxmind.geoip2.record; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.maxmind.geoip2.JsonSerializable; +import java.time.LocalDate; + +/** + *

+ * Contains data for one type of anonymizer detection, currently residential proxies. Additional + * feeds, such as VPNs, mobile networks, and hosting or datacenter providers, may be added to the + * {@link Anonymizer} record in the future using this same record type. + *

+ *

+ * This record is returned by the GeoIP Insights web service. + *

+ * + * @param confidence A score ranging from 1 to 99 that represents our percent confidence that + * the network is an actively used residential proxy. This is only available + * from the GeoIP Insights web service. + * @param networkLastSeen The last day that the network was sighted in our analysis of + * residential proxies. This is only available from the GeoIP Insights + * web service. + * @param providerName The name of the residential proxy provider associated with the network. + * This is only available from the GeoIP Insights web service. + */ +public record AnonymizerFeed( + @JsonProperty("confidence") + Integer confidence, + + @JsonProperty("network_last_seen") + LocalDate networkLastSeen, + + @JsonProperty("provider_name") + String providerName +) implements JsonSerializable { + + /** + * Constructs an {@code AnonymizerFeed} record with {@code null} values for all fields. + */ + public AnonymizerFeed() { + this(null, null, null); + } +} diff --git a/src/test/java/com/maxmind/geoip2/model/InsightsResponseTest.java b/src/test/java/com/maxmind/geoip2/model/InsightsResponseTest.java index 38905e77..f4081dd4 100644 --- a/src/test/java/com/maxmind/geoip2/model/InsightsResponseTest.java +++ b/src/test/java/com/maxmind/geoip2/model/InsightsResponseTest.java @@ -16,6 +16,7 @@ import com.maxmind.geoip2.exception.GeoIp2Exception; import com.maxmind.geoip2.model.ConnectionTypeResponse.ConnectionType; import com.maxmind.geoip2.record.Anonymizer; +import com.maxmind.geoip2.record.AnonymizerFeed; import com.maxmind.geoip2.record.Location; import com.maxmind.geoip2.record.MaxMind; import com.maxmind.geoip2.record.Postal; @@ -206,6 +207,25 @@ public void testAnonymizer() { anonymizer.providerName(), "anonymizer.providerName() does not return NordVPN" ); + + AnonymizerFeed residential = anonymizer.residential(); + + assertNotNull(residential, "anonymizer.residential() returns null"); + assertEquals( + Integer.valueOf(82), + residential.confidence(), + "anonymizer.residential().confidence() does not return 82" + ); + assertEquals( + LocalDate.parse("2026-05-11"), + residential.networkLastSeen(), + "anonymizer.residential().networkLastSeen() does not return 2026-05-11" + ); + assertEquals( + "quickshift", + residential.providerName(), + "anonymizer.residential().providerName() does not return quickshift" + ); } @Test diff --git a/src/test/java/com/maxmind/geoip2/model/JsonTest.java b/src/test/java/com/maxmind/geoip2/model/JsonTest.java index 97e69a8b..f75157f2 100644 --- a/src/test/java/com/maxmind/geoip2/model/JsonTest.java +++ b/src/test/java/com/maxmind/geoip2/model/JsonTest.java @@ -62,6 +62,11 @@ public void testInsightsSerialization() throws IOException { .put("is_tor_exit_node", true) .put("network_last_seen", "2024-12-31") .put("provider_name", "NordVPN") + .startObjectField("residential") + .put("confidence", 82) + .put("network_last_seen", "2026-05-11") + .put("provider_name", "quickshift") + .end() .end() .startObjectField("country") .startObjectField("names") diff --git a/src/test/resources/test-data/insights0.json b/src/test/resources/test-data/insights0.json index 45118585..1f56bb33 100644 --- a/src/test/resources/test-data/insights0.json +++ b/src/test/resources/test-data/insights0.json @@ -94,6 +94,11 @@ "is_residential_proxy": true, "is_tor_exit_node": true, "network_last_seen": "2024-12-31", - "provider_name": "NordVPN" + "provider_name": "NordVPN", + "residential": { + "confidence": 82, + "network_last_seen": "2026-05-11", + "provider_name": "quickshift" + } } } \ No newline at end of file diff --git a/src/test/resources/test-data/insights1.json b/src/test/resources/test-data/insights1.json index 6cc70cba..666bcbb1 100644 --- a/src/test/resources/test-data/insights1.json +++ b/src/test/resources/test-data/insights1.json @@ -110,6 +110,11 @@ "is_residential_proxy": true, "is_tor_exit_node": true, "network_last_seen": "2024-12-31", - "provider_name": "NordVPN" + "provider_name": "NordVPN", + "residential": { + "confidence": 82, + "network_last_seen": "2026-05-11", + "provider_name": "quickshift" + } } } \ No newline at end of file