Skip to content

Commit b2d6d88

Browse files
committed
Add support for Anonymous Plus
1 parent dc6507e commit b2d6d88

File tree

9 files changed

+282
-0
lines changed

9 files changed

+282
-0
lines changed

CHANGELOG.md

+3
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ CHANGELOG
44
4.3.0
55
------------------
66

7+
* Support for the GeoIP Anonymous Plus database has been added. To do a
8+
lookup in this database, use the `anonymousPlus` method on
9+
`DatabaseReader`.
710
* `getMetroCode` in the `Location` model has been deprecated. The code
811
values are no longer being maintained.
912

README.md

+26
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,32 @@ try (DatabaseReader reader = new DatabaseReader.Builder(database).build()) {
307307

308308
```
309309

310+
### Anonymous Plus ###
311+
312+
```java
313+
// A File object pointing to your GeoIP2 Anonymous IP database
314+
File database = new File("/path/to/GeoIP-Anonymous-Plus.mmdb");
315+
316+
// This creates the DatabaseReader object. To improve performance, reuse
317+
// the object across lookups. The object is thread-safe.
318+
try (DatabaseReader reader = new DatabaseReader.Builder(database).build()) {
319+
InetAddress ipAddress = InetAddress.getByName("85.25.43.84");
320+
321+
AnonymousIpResponse response = reader.anonymousPlus(ipAddress);
322+
323+
System.out.println(response.anonymizerConfidence()); // 30
324+
System.out.println(response.isAnonymous()); // true
325+
System.out.println(response.isAnonymousVpn()); // false
326+
System.out.println(response.isHostingProvider()); // false
327+
System.out.println(response.isPublicProxy()); // false
328+
System.out.println(response.isResidentialProxy()); // false
329+
System.out.println(response.isTorExitNode()); // true
330+
System.out.println(response.networkLastSeen()); // "2025-04-14"
331+
System.out.println(response.providerName()); // "FooBar VPN"
332+
}
333+
334+
```
335+
310336
### ASN ###
311337

312338
```java

pom.xml

+5
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,11 @@
4747
<artifactId>jackson-databind</artifactId>
4848
<version>2.18.3</version>
4949
</dependency>
50+
<dependency>
51+
<groupId>com.fasterxml.jackson.datatype</groupId>
52+
<artifactId>jackson-datatype-jsr310</artifactId>
53+
<version>2.18.3</version>
54+
</dependency>
5055
<dependency>
5156
<groupId>com.fasterxml.jackson.core</groupId>
5257
<artifactId>jackson-core</artifactId>

src/main/java/com/maxmind/geoip2/DatabaseProvider.java

+22
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import com.maxmind.geoip2.exception.GeoIp2Exception;
44
import com.maxmind.geoip2.model.AnonymousIpResponse;
5+
import com.maxmind.geoip2.model.AnonymousPlusResponse;
56
import com.maxmind.geoip2.model.AsnResponse;
67
import com.maxmind.geoip2.model.CityResponse;
78
import com.maxmind.geoip2.model.ConnectionTypeResponse;
@@ -59,6 +60,27 @@ AnonymousIpResponse anonymousIp(InetAddress ipAddress) throws IOException,
5960
Optional<AnonymousIpResponse> tryAnonymousIp(InetAddress ipAddress) throws IOException,
6061
GeoIp2Exception;
6162

63+
/**
64+
* Look up an IP address in a GeoIP2 Anonymous Plus.
65+
*
66+
* @param ipAddress IPv4 or IPv6 address to lookup.
67+
* @return a AnonymousIpResponse for the requested IP address.
68+
* @throws com.maxmind.geoip2.exception.GeoIp2Exception if there is an error looking up the IP
69+
* @throws java.io.IOException if there is an IO error
70+
*/
71+
AnonymousPlusResponse anonymousPlus(InetAddress ipAddress) throws IOException,
72+
GeoIp2Exception;
73+
74+
/**
75+
* Look up an IP address in a GeoIP2 Anonymous Plus.
76+
*
77+
* @param ipAddress IPv4 or IPv6 address to lookup.
78+
* @return a AnonymousIpResponse for the requested IP address or empty if it is not in the DB.
79+
* @throws com.maxmind.geoip2.exception.GeoIp2Exception if there is an error looking up the IP
80+
* @throws java.io.IOException if there is an IO error
81+
*/
82+
Optional<AnonymousPlusResponse> tryAnonymousPlus(InetAddress ipAddress) throws IOException,
83+
GeoIp2Exception;
6284

6385
/**
6486
* Look up an IP address in a GeoIP2 IP Risk database.

src/main/java/com/maxmind/geoip2/DatabaseReader.java

+53
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import com.maxmind.geoip2.exception.AddressNotFoundException;
1111
import com.maxmind.geoip2.exception.GeoIp2Exception;
1212
import com.maxmind.geoip2.model.AnonymousIpResponse;
13+
import com.maxmind.geoip2.model.AnonymousPlusResponse;
1314
import com.maxmind.geoip2.model.AsnResponse;
1415
import com.maxmind.geoip2.model.CityResponse;
1516
import com.maxmind.geoip2.model.ConnectionTypeResponse;
@@ -81,6 +82,7 @@ public class DatabaseReader implements DatabaseProvider, Closeable {
8182

8283
private enum DatabaseType {
8384
ANONYMOUS_IP,
85+
ANONYMOUS_PLUS,
8486
ASN,
8587
CITY,
8688
CONNECTION_TYPE,
@@ -119,6 +121,9 @@ private int getDatabaseType() {
119121
if (databaseType.contains("GeoIP2-Anonymous-IP")) {
120122
type |= DatabaseType.ANONYMOUS_IP.type;
121123
}
124+
if (databaseType.contains("GeoIP-Anonymous-Plus")) {
125+
type |= DatabaseType.ANONYMOUS_PLUS.type;
126+
}
122127
if (databaseType.contains("GeoIP2-IP-Risk")) {
123128
type |= DatabaseType.IP_RISK.type;
124129
}
@@ -427,6 +432,54 @@ private Optional<AnonymousIpResponse> getAnonymousIp(
427432
);
428433
}
429434

435+
/**
436+
* Look up an IP address in a GeoIP2 Anonymous IP.
437+
*
438+
* @param ipAddress IPv4 or IPv6 address to lookup.
439+
* @return a AnonymousPlusResponse for the requested IP address.
440+
* @throws GeoIp2Exception if there is an error looking up the IP
441+
* @throws IOException if there is an IO error
442+
*/
443+
@Override
444+
public AnonymousPlusResponse anonymousPlus(InetAddress ipAddress) throws IOException,
445+
GeoIp2Exception {
446+
Optional<AnonymousPlusResponse> r = getAnonymousPlus(ipAddress);
447+
if (r.isEmpty()) {
448+
throw new AddressNotFoundException("The address "
449+
+ ipAddress.getHostAddress() + " is not in the database.");
450+
}
451+
return r.get();
452+
}
453+
454+
@Override
455+
public Optional<AnonymousPlusResponse> tryAnonymousPlus(InetAddress ipAddress)
456+
throws IOException,
457+
GeoIp2Exception {
458+
return getAnonymousPlus(ipAddress);
459+
}
460+
461+
private Optional<AnonymousPlusResponse> getAnonymousPlus(
462+
InetAddress ipAddress
463+
) throws IOException, GeoIp2Exception {
464+
LookupResult<AnonymousPlusResponse> result = this.get(
465+
ipAddress,
466+
AnonymousPlusResponse.class,
467+
DatabaseType.ANONYMOUS_PLUS
468+
);
469+
AnonymousPlusResponse response = result.getModel();
470+
if (response == null) {
471+
return Optional.empty();
472+
}
473+
return Optional.of(
474+
new AnonymousPlusResponse(
475+
response,
476+
result.getIpAddress(),
477+
result.getNetwork()
478+
)
479+
);
480+
}
481+
482+
430483
/**
431484
* Look up an IP address in a GeoIP2 IP Risk database.
432485
*

src/main/java/com/maxmind/geoip2/model/AbstractResponse.java

+2
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import com.fasterxml.jackson.annotation.JsonInclude.Include;
44
import com.fasterxml.jackson.databind.MapperFeature;
55
import com.fasterxml.jackson.databind.json.JsonMapper;
6+
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
67
import java.io.IOException;
78

89
/**
@@ -18,6 +19,7 @@ public abstract class AbstractResponse {
1819
public String toJson() throws IOException {
1920
JsonMapper mapper = JsonMapper.builder()
2021
.disable(MapperFeature.CAN_OVERRIDE_ACCESS_MODIFIERS)
22+
.addModule(new JavaTimeModule())
2123
.serializationInclusion(Include.NON_NULL)
2224
.serializationInclusion(Include.NON_EMPTY)
2325
.build();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
package com.maxmind.geoip2.model;
2+
3+
import com.fasterxml.jackson.annotation.JacksonInject;
4+
import com.fasterxml.jackson.annotation.JsonProperty;
5+
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
6+
import com.maxmind.db.MaxMindDbConstructor;
7+
import com.maxmind.db.MaxMindDbParameter;
8+
import com.maxmind.db.Network;
9+
import com.maxmind.geoip2.NetworkDeserializer;
10+
import java.time.LocalDate;
11+
12+
/**
13+
* This class provides the GeoIP Anonymous Plus model.
14+
*/
15+
public class AnonymousPlusResponse extends AnonymousIpResponse {
16+
private final Integer anonymizerConfidence;
17+
private final LocalDate networkLastSeen;
18+
private final String providerName;
19+
20+
/**
21+
* Constructs an instance of {@code AnonymousPlusResponse} with the specified values.
22+
*
23+
* @param anonymizerConfidence confidence that the network is a VPN.
24+
* @param ipAddress the IP address being checked
25+
* @param isAnonymous whether the IP address belongs to any sort of anonymous network
26+
* @param isAnonymousVpn whether the IP address belongs to an anonymous VPN system
27+
* @param isHostingProvider whether the IP address belongs to a hosting provider
28+
* @param isPublicProxy whether the IP address belongs to a public proxy system
29+
* @param isResidentialProxy whether the IP address belongs to a residential proxy system
30+
* @param isTorExitNode whether the IP address is a Tor exit node
31+
* @param network the network associated with the record
32+
* @param networkLastSeen the last sighting of the network.
33+
* @param providerName the name of the VPN provider.
34+
*/
35+
public AnonymousPlusResponse(
36+
@JsonProperty("anonymizer_confidence") Integer anonymizerConfidence,
37+
@JacksonInject("ip_address") @JsonProperty("ip_address") String ipAddress,
38+
@JsonProperty("is_anonymous") Boolean isAnonymous,
39+
@JsonProperty("is_anonymous_vpn") Boolean isAnonymousVpn,
40+
@JsonProperty("is_hosting_provider") Boolean isHostingProvider,
41+
@JsonProperty("is_public_proxy") Boolean isPublicProxy,
42+
@JsonProperty("is_residential_proxy") Boolean isResidentialProxy,
43+
@JsonProperty("is_tor_exit_node") Boolean isTorExitNode,
44+
@JacksonInject("network") @JsonDeserialize(using = NetworkDeserializer.class)
45+
@JsonProperty("network") Network network,
46+
@JsonProperty("network_last_seen") LocalDate networkLastSeen,
47+
@JsonProperty("provider_name") String providerName
48+
) {
49+
super(ipAddress, isAnonymous, isAnonymousVpn, isHostingProvider, isPublicProxy,
50+
isResidentialProxy, isTorExitNode, network);
51+
52+
this.anonymizerConfidence = anonymizerConfidence;
53+
this.networkLastSeen = networkLastSeen;
54+
this.providerName = providerName;
55+
}
56+
57+
/**
58+
* Constructs an instance of {@code AnonymousPlusResponse} with the specified values.
59+
*
60+
* @param anonymizerConfidence confidence that the network is a VPN.
61+
* @param ipAddress the IP address being checked
62+
* @param isAnonymous whether the IP address belongs to any sort of anonymous network
63+
* @param isAnonymousVpn whether the IP address belongs to an anonymous VPN system
64+
* @param isHostingProvider whether the IP address belongs to a hosting provider
65+
* @param isPublicProxy whether the IP address belongs to a public proxy system
66+
* @param isResidentialProxy whether the IP address belongs to a residential proxy system
67+
* @param isTorExitNode whether the IP address is a Tor exit node
68+
* @param network the network associated with the record
69+
* @param networkLastSeen the last sighting of the network.
70+
* @param providerName the name of the VPN provider.
71+
*/
72+
@MaxMindDbConstructor
73+
public AnonymousPlusResponse(
74+
@MaxMindDbParameter(name = "anonymizer_confidence") Integer anonymizerConfidence,
75+
@MaxMindDbParameter(name = "ip_address") String ipAddress,
76+
@MaxMindDbParameter(name = "is_anonymous") Boolean isAnonymous,
77+
@MaxMindDbParameter(name = "is_anonymous_vpn") Boolean isAnonymousVpn,
78+
@MaxMindDbParameter(name = "is_hosting_provider") Boolean isHostingProvider,
79+
@MaxMindDbParameter(name = "is_public_proxy") Boolean isPublicProxy,
80+
@MaxMindDbParameter(name = "is_residential_proxy") Boolean isResidentialProxy,
81+
@MaxMindDbParameter(name = "is_tor_exit_node") Boolean isTorExitNode,
82+
@MaxMindDbParameter(name = "network") Network network,
83+
@MaxMindDbParameter(name = "network_last_seen") String networkLastSeen,
84+
@MaxMindDbParameter(name = "provider_name") String providerName
85+
) {
86+
this(anonymizerConfidence, ipAddress, isAnonymous, isAnonymousVpn,
87+
isHostingProvider, isPublicProxy, isResidentialProxy, isTorExitNode, network,
88+
networkLastSeen != null ? LocalDate.parse(networkLastSeen) : null,
89+
providerName);
90+
}
91+
92+
/**
93+
* Constructs an instance of {@code AnonymousPlusResponse} from the values in the
94+
* response and the specified IP address and network.
95+
*
96+
* @param response the response to copy
97+
* @param ipAddress the IP address being checked
98+
* @param network the network associated with the record
99+
*/
100+
public AnonymousPlusResponse(
101+
AnonymousPlusResponse response,
102+
String ipAddress,
103+
Network network
104+
) {
105+
this(
106+
response.getAnonymizerConfidence(),
107+
ipAddress,
108+
response.isAnonymous(),
109+
response.isAnonymousVpn(),
110+
response.isHostingProvider(),
111+
response.isPublicProxy(),
112+
response.isResidentialProxy(),
113+
response.isTorExitNode(),
114+
network,
115+
response.getNetworkLastSeen(),
116+
response.getProviderName()
117+
);
118+
}
119+
120+
/**
121+
* @return A score ranging from 1 to 99 that is our percent confidence that the network is
122+
* currently part of an actively used VPN service.
123+
*/
124+
@JsonProperty
125+
public Integer getAnonymizerConfidence() {
126+
return anonymizerConfidence;
127+
}
128+
129+
/**
130+
* @return The last day that the network was sighted in our analysis of anonymized networks.
131+
*/
132+
@JsonProperty
133+
public LocalDate getNetworkLastSeen() {
134+
return networkLastSeen;
135+
}
136+
137+
/**
138+
* @return The name of the VPN provider (e.g., NordVPN, SurfShark, etc.) associated with the
139+
* network.
140+
*/
141+
@JsonProperty
142+
public String getProviderName() {
143+
return providerName;
144+
}
145+
}

src/main/java/module-info.java

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
module com.maxmind.geoip2 {
33
requires com.fasterxml.jackson.annotation;
44
requires com.fasterxml.jackson.databind;
5+
requires com.fasterxml.jackson.datatype.jsr310;
56
requires transitive com.maxmind.db;
67
requires java.net.http;
78

src/test/java/com/maxmind/geoip2/DatabaseReaderTest.java

+25
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import com.maxmind.geoip2.exception.AddressNotFoundException;
1212
import com.maxmind.geoip2.exception.GeoIp2Exception;
1313
import com.maxmind.geoip2.model.AnonymousIpResponse;
14+
import com.maxmind.geoip2.model.AnonymousPlusResponse;
1415
import com.maxmind.geoip2.model.AsnResponse;
1516
import com.maxmind.geoip2.model.CityResponse;
1617
import com.maxmind.geoip2.model.ConnectionTypeResponse;
@@ -227,6 +228,30 @@ public void testAnonymousIp() throws Exception {
227228
}
228229
}
229230

231+
@Test
232+
public void testAnonymousPlus() throws Exception {
233+
try (DatabaseReader reader = new DatabaseReader.Builder(
234+
this.getFile("GeoIP-Anonymous-Plus-Test.mmdb")).build()
235+
) {
236+
InetAddress ipAddress = InetAddress.getByName("1.2.0.1");
237+
AnonymousPlusResponse response = reader.anonymousPlus(ipAddress);
238+
assertEquals(30, response.getAnonymizerConfidence());
239+
assertTrue(response.isAnonymous());
240+
assertTrue(response.isAnonymousVpn());
241+
assertFalse(response.isHostingProvider());
242+
assertFalse(response.isPublicProxy());
243+
assertFalse(response.isResidentialProxy());
244+
assertFalse(response.isTorExitNode());
245+
assertEquals(ipAddress.getHostAddress(), response.getIpAddress());
246+
assertEquals("1.2.0.1/32", response.getNetwork().toString());
247+
assertEquals("2025-04-14", response.getNetworkLastSeen().toString());
248+
assertEquals("foo", response.getProviderName());
249+
250+
AnonymousPlusResponse tryResponse = reader.tryAnonymousPlus(ipAddress).get();
251+
assertEquals(response.toJson(), tryResponse.toJson());
252+
}
253+
}
254+
230255
@Test
231256
public void testAnonymousIpIsResidentialProxy() throws Exception {
232257
try (DatabaseReader reader = new DatabaseReader.Builder(

0 commit comments

Comments
 (0)