Skip to content

Commit 0f17d19

Browse files
committed
Add ability to get network for the lookup
1 parent d0bcfba commit 0f17d19

File tree

5 files changed

+226
-28
lines changed

5 files changed

+226
-28
lines changed
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package com.maxmind.db;
2+
3+
import java.net.InetAddress;
4+
import java.net.UnknownHostException;
5+
6+
/**
7+
* <code>Network</code> represents an IP network.
8+
*/
9+
public final class Network {
10+
private final InetAddress ipAddress;
11+
private final int prefixLength;
12+
private InetAddress networkAddress = null;
13+
14+
/**
15+
* Construct a <code>Network</code>
16+
*
17+
* @param ipAddress An IP address in the network. This does not have to be
18+
* the first address in the network.
19+
* @param prefixLength The prefix length for the network.
20+
*/
21+
public Network(InetAddress ipAddress, int prefixLength) {
22+
this.ipAddress = ipAddress;
23+
this.prefixLength = prefixLength;
24+
}
25+
26+
/**
27+
* @return The first address in the network.
28+
*/
29+
public InetAddress getNetworkAddress() {
30+
if (networkAddress != null) {
31+
return networkAddress;
32+
}
33+
byte[] ipBytes = ipAddress.getAddress();
34+
byte[] networkBytes = new byte[ipBytes.length];
35+
int curPrefix = prefixLength;
36+
for (int i = 0; i < ipBytes.length && curPrefix > 0; i++) {
37+
byte b = ipBytes[i];
38+
if (curPrefix < 8) {
39+
int shiftN = 8 - curPrefix;
40+
b = (byte) ((b >> shiftN) << shiftN);
41+
}
42+
networkBytes[i] = b;
43+
curPrefix -= 8;
44+
}
45+
46+
try {
47+
networkAddress = InetAddress.getByAddress(networkBytes);
48+
} catch (UnknownHostException e) {
49+
throw new RuntimeException("Illegal network address byte length of " + networkBytes.length);
50+
}
51+
return networkAddress;
52+
}
53+
54+
/**
55+
* @return The prefix length is the number of leading 1 bits in the subnet
56+
* mask. Sometimes also known as netmask length.
57+
*/
58+
public int getPrefixLength() {
59+
return prefixLength;
60+
}
61+
62+
/***
63+
* @return A string representation of the network in CIDR notation, e.g.,
64+
* <code>1.2.3.0/24</code> or <code>2001::/8</code>.
65+
*/
66+
public String toString() {
67+
return getNetworkAddress().getHostAddress() + "/" + prefixLength;
68+
}
69+
}

src/main/java/com/maxmind/db/Reader.java

Lines changed: 33 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -135,50 +135,55 @@ private Reader(BufferHolder bufferHolder, String name, NodeCache cache) throws I
135135
}
136136

137137
/**
138-
* Looks up the <code>address</code> in the MaxMind DB.
138+
* Looks up <code>ipAddress</code> in the MaxMind DB.
139139
*
140140
* @param ipAddress the IP address to look up.
141-
* @return the record for the IP address.
141+
* @return the record data for the IP address.
142142
* @throws IOException if a file I/O error occurs.
143143
*/
144144
public JsonNode get(InetAddress ipAddress) throws IOException {
145-
ByteBuffer buffer = this.getBufferHolder().get();
146-
int pointer = this.findAddressInTree(buffer, ipAddress);
147-
if (pointer == 0) {
148-
return null;
149-
}
150-
return this.resolveDataPointer(buffer, pointer);
151-
}
152-
153-
private BufferHolder getBufferHolder() throws ClosedDatabaseException {
154-
BufferHolder bufferHolder = this.bufferHolderReference.get();
155-
if (bufferHolder == null) {
156-
throw new ClosedDatabaseException();
157-
}
158-
return bufferHolder;
145+
return getRecord(ipAddress).getData();
159146
}
147+
/**
148+
* Looks up <code>ipAddress</code> in the MaxMind DB.
149+
*
150+
* @param ipAddress the IP address to look up.
151+
* @return the record for the IP address. If there is no data for the
152+
* address, the non-null {@link Record} will still be returned.
153+
* @throws IOException if a file I/O error occurs.
154+
*/
155+
public Record getRecord(InetAddress ipAddress)
156+
throws IOException {
157+
ByteBuffer buffer = this.getBufferHolder().get();
160158

161-
private int findAddressInTree(ByteBuffer buffer, InetAddress address)
162-
throws InvalidDatabaseException {
163-
byte[] rawAddress = address.getAddress();
159+
byte[] rawAddress = ipAddress.getAddress();
164160

165161
int bitLength = rawAddress.length * 8;
166162
int record = this.startNode(bitLength);
167163
int nodeCount = this.metadata.getNodeCount();
168164

169-
for (int i = 0; i < bitLength && record < nodeCount; i++) {
170-
int b = 0xFF & rawAddress[i / 8];
171-
int bit = 1 & (b >> 7 - (i % 8));
165+
int pl = 0;
166+
for (; pl < bitLength && record < nodeCount; pl++) {
167+
int b = 0xFF & rawAddress[pl / 8];
168+
int bit = 1 & (b >> 7 - (pl % 8));
172169
record = this.readNode(buffer, record, bit);
173170
}
174-
if (record == nodeCount) {
175-
// record is empty
176-
return 0;
177-
} else if (record > nodeCount) {
171+
172+
JsonNode dataRecord = null;
173+
if (record > nodeCount) {
178174
// record is a data pointer
179-
return record;
175+
dataRecord = this.resolveDataPointer(buffer, record);
176+
}
177+
178+
return new Record(dataRecord, ipAddress, pl);
179+
}
180+
181+
private BufferHolder getBufferHolder() throws ClosedDatabaseException {
182+
BufferHolder bufferHolder = this.bufferHolderReference.get();
183+
if (bufferHolder == null) {
184+
throw new ClosedDatabaseException();
180185
}
181-
throw new InvalidDatabaseException("Something bad happened");
186+
return bufferHolder;
182187
}
183188

184189
private int startNode(int bitLength) {
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package com.maxmind.db;
2+
3+
import com.fasterxml.jackson.databind.JsonNode;
4+
5+
import java.net.InetAddress;
6+
7+
/**
8+
* Record represents the data and metadata associated with a database lookup.
9+
*/
10+
public final class Record {
11+
private final JsonNode data;
12+
private final Network network;
13+
14+
/**
15+
* Create a new record.
16+
*
17+
* @param data the data for the record in the database.
18+
* @param ipAddress the IP address used in the lookup.
19+
* @param prefixLength the network prefix length associated with the record in the database.
20+
*/
21+
public Record( JsonNode data, InetAddress ipAddress, int prefixLength) {
22+
this.data = data;
23+
this.network = new Network(ipAddress, prefixLength);
24+
}
25+
26+
/**
27+
* @return the data for the record in the database. The record will be
28+
* <code>null</code> if there was no data for the address in the database.
29+
*/
30+
public JsonNode getData() {
31+
return data;
32+
}
33+
34+
/**
35+
* @return the network associated with the record in the database. This is
36+
* the largest network where all of the IPs in the network have the same
37+
* data.
38+
*/
39+
public Network getNetwork() {
40+
return network;
41+
}
42+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package com.maxmind.db;
2+
3+
import org.junit.Test;
4+
5+
import java.net.InetAddress;
6+
import java.net.UnknownHostException;
7+
8+
import static junit.framework.TestCase.assertEquals;
9+
10+
public class NetworkTest {
11+
@Test
12+
public void testIPv6() throws UnknownHostException {
13+
Network network = new Network(
14+
InetAddress.getByName("2001:0db8:85a3:0000:0000:8a2e:0370:7334"),
15+
28
16+
);
17+
18+
assertEquals("2001:db0:0:0:0:0:0:0", network.getNetworkAddress().getHostAddress());
19+
assertEquals(28, network.getPrefixLength());
20+
assertEquals("2001:db0:0:0:0:0:0:0/28", network.toString());
21+
}
22+
23+
@Test
24+
public void TestIPv4() throws UnknownHostException {
25+
Network network = new Network(
26+
InetAddress.getByName("192.168.213.111"),
27+
31
28+
);
29+
30+
assertEquals("192.168.213.110", network.getNetworkAddress().getHostAddress());
31+
assertEquals(31, network.getPrefixLength());
32+
assertEquals("192.168.213.110/31", network.toString());
33+
}
34+
35+
}

src/test/java/com/maxmind/db/ReaderTest.java

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import java.io.InputStream;
1616
import java.math.BigInteger;
1717
import java.net.InetAddress;
18+
import java.net.UnknownHostException;
1819
import java.util.*;
1920

2021
import static org.hamcrest.CoreMatchers.containsString;
@@ -54,6 +55,52 @@ public void test() throws IOException {
5455
}
5556
}
5657

58+
class GetRecordTest {
59+
InetAddress ip;
60+
File db;
61+
String network;
62+
boolean hasRecord;
63+
64+
GetRecordTest(String ip, String file, String network, boolean hasRecord) throws UnknownHostException {
65+
this.ip = InetAddress.getByName(ip);
66+
db = getFile(file);
67+
this.network = network;
68+
this.hasRecord = hasRecord;
69+
}
70+
}
71+
72+
@Test
73+
public void testGetRecord() throws IOException {
74+
GetRecordTest[] tests = {
75+
new GetRecordTest("1.1.1.1", "MaxMind-DB-test-ipv6-32.mmdb", "1.0.0.0/8", false),
76+
new GetRecordTest("::1:ffff:ffff", "MaxMind-DB-test-ipv6-24.mmdb", "0:0:0:0:0:1:ffff:ffff/128", true),
77+
new GetRecordTest("::2:0:1", "MaxMind-DB-test-ipv6-24.mmdb", "0:0:0:0:0:2:0:0/122", true),
78+
new GetRecordTest("1.1.1.1", "MaxMind-DB-test-ipv4-24.mmdb", "1.1.1.1/32", true),
79+
new GetRecordTest("1.1.1.3", "MaxMind-DB-test-ipv4-24.mmdb", "1.1.1.2/31", true),
80+
new GetRecordTest("1.1.1.3", "MaxMind-DB-test-decoder.mmdb", "1.1.1.0/24", true),
81+
new GetRecordTest("::ffff:1.1.1.128", "MaxMind-DB-test-decoder.mmdb", "1.1.1.0/24", true),
82+
new GetRecordTest("::1.1.1.128", "MaxMind-DB-test-decoder.mmdb", "0:0:0:0:0:0:101:100/120", true),
83+
new GetRecordTest("200.0.2.1", "MaxMind-DB-no-ipv4-search-tree.mmdb", "0.0.0.0/0", true),
84+
new GetRecordTest("::200.0.2.1", "MaxMind-DB-no-ipv4-search-tree.mmdb", "0:0:0:0:0:0:0:0/64", true),
85+
new GetRecordTest("0:0:0:0:ffff:ffff:ffff:ffff", "MaxMind-DB-no-ipv4-search-tree.mmdb", "0:0:0:0:0:0:0:0/64", true),
86+
new GetRecordTest("ef00::", "MaxMind-DB-no-ipv4-search-tree.mmdb", "8000:0:0:0:0:0:0:0/1", false)
87+
};
88+
for (GetRecordTest test : tests) {
89+
try (Reader reader = new Reader(test.db)) {
90+
Record record = reader.getRecord(test.ip);
91+
92+
assertEquals(test.network, record.getNetwork().toString());
93+
94+
if (test.hasRecord) {
95+
assertNotNull(record.getData());
96+
} else {
97+
assertNull(record.getData());
98+
}
99+
}
100+
}
101+
}
102+
103+
57104
@Test
58105
public void testNoIpV4SearchTreeFile() throws IOException {
59106
this.testReader = new Reader(getFile("MaxMind-DB-no-ipv4-search-tree.mmdb"));

0 commit comments

Comments
 (0)