Skip to content

Commit a001e50

Browse files
committed
* ip: support ipv6
Signed-off-by: neo <1100909+neowu@users.noreply.github.com>
1 parent b638e05 commit a001e50

25 files changed

+247
-181
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
### 9.2.2 (5/21/2025 - )
44

55
* http_client: support proxy
6+
* ip: support ipv6
7+
> support ipv6 ranges in access control
8+
> !!! IPRangePropertyValueParser parsing behavior slightly changed, "NAME: CIDR1, CIDR2;", the "NAME:" must have space after it
69
710
### 9.2.1 (4/24/2025 - 5/19/2025) !!! only support java 24
811

core-ng-test/src/test/java/core/framework/test/TestModule.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,8 @@ private void configureHTTP() {
9393
http().maxEntitySize(10000000);
9494
http().access().allow(List.of("0.0.0.0/0"));
9595
http().access().deny(List.of("10.0.0.0/24"));
96+
http().access().allowIPv6(List.of("::/0"));
97+
http().access().denyIPv6(List.of("2001:df0:465::/48"));
9698
http().errorHandler((request, e) -> Optional.empty());
9799
}
98100

core-ng/src/main/java/core/framework/internal/web/HTTPHandlerContext.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import core.framework.internal.web.bean.RequestBeanReader;
44
import core.framework.internal.web.bean.ResponseBeanWriter;
5-
import core.framework.internal.web.http.IPv4AccessControl;
5+
import core.framework.internal.web.http.IPAccessControl;
66
import core.framework.internal.web.http.RateControl;
77
import core.framework.internal.web.request.RequestParser;
88

@@ -15,5 +15,5 @@ public class HTTPHandlerContext {
1515
@Nullable
1616
public RateControl rateControl;
1717
@Nullable
18-
public IPv4AccessControl accessControl;
18+
public IPAccessControl accessControl;
1919
}

core-ng/src/main/java/core/framework/internal/web/http/IPv4AccessControl.java renamed to core-ng/src/main/java/core/framework/internal/web/http/IPAccessControl.java

Lines changed: 28 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -10,37 +10,48 @@
1010
/**
1111
* @author neo
1212
*/
13-
public class IPv4AccessControl {
14-
private final Logger logger = LoggerFactory.getLogger(IPv4AccessControl.class);
13+
public class IPAccessControl {
14+
private final Logger logger = LoggerFactory.getLogger(IPAccessControl.class);
1515
public IPv4Ranges allow;
1616
public IPv4Ranges deny;
17+
public IPv6Ranges allowIPv6;
18+
public IPv6Ranges denyIPv6;
1719

1820
public void validate(String clientIP) {
21+
InetAddress address;
1922
try {
20-
InetAddress address = InetAddress.getByName(clientIP);
21-
if (isLocal(address)) {
22-
logger.debug("allow site local client address");
23-
return;
24-
}
25-
26-
if (!allow(address.getAddress())) {
27-
throw new ForbiddenException("access denied", "IP_ACCESS_DENIED");
28-
}
23+
address = InetAddress.getByName(clientIP);
2924
} catch (UnknownHostException e) {
3025
throw new Error(e); // client ip format is already validated in ClientIPParser, so here it won't resolve DNS
3126
}
27+
28+
if (isLocal(address)) {
29+
logger.debug("allow site local client address");
30+
return;
31+
}
32+
33+
byte[] byteAddress = address.getAddress();
34+
IPRanges allow;
35+
IPRanges deny;
36+
if (byteAddress.length == 4) {
37+
allow = this.allow;
38+
deny = this.deny;
39+
} else if (byteAddress.length == 16) {
40+
allow = allowIPv6;
41+
deny = denyIPv6;
42+
} else {
43+
throw new Error("unexpected address, address=" + address);
44+
}
45+
if (!allow(byteAddress, allow, deny)) {
46+
throw new ForbiddenException("access denied", "IP_ACCESS_DENIED");
47+
}
3248
}
3349

3450
boolean isLocal(InetAddress address) {
3551
return address.isLoopbackAddress() || address.isSiteLocalAddress();
3652
}
3753

38-
boolean allow(byte[] address) {
39-
if (address.length > 4) { // only support ipv4, as Cloud LB generally uses ipv4 endpoint (gcloud supports both ipv4/v6, but ipv6 is not majority yet)
40-
logger.debug("skip with ipv6 client address");
41-
return true;
42-
}
43-
54+
boolean allow(byte[] address, IPRanges allow, IPRanges deny) {
4455
if (allow != null && allow.matches(address)) {
4556
logger.debug("allow client ip within allowed ranges");
4657
return true;
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package core.framework.internal.web.http;
2+
3+
import java.net.InetAddress;
4+
import java.net.UnknownHostException;
5+
6+
public interface IPRanges {
7+
static byte[] address(String address) {
8+
try {
9+
return InetAddress.getByName(address).getAddress();
10+
} catch (UnknownHostException e) {
11+
throw new Error(e);
12+
}
13+
}
14+
15+
boolean matches(byte[] address);
16+
}

core-ng/src/main/java/core/framework/internal/web/http/IPv4Ranges.java

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,13 @@
11
package core.framework.internal.web.http;
22

3-
import java.net.InetAddress;
4-
import java.net.UnknownHostException;
53
import java.util.Arrays;
64
import java.util.Comparator;
75
import java.util.List;
86

97
/**
108
* @author neo
119
*/
12-
public class IPv4Ranges {
13-
static byte[] address(String address) {
14-
try {
15-
return InetAddress.getByName(address).getAddress();
16-
} catch (UnknownHostException e) {
17-
throw new Error(e);
18-
}
19-
}
20-
10+
public class IPv4Ranges implements IPRanges {
2111
static boolean withinRanges(int[] ranges, int value) {
2212
int low = 0;
2313
int high = ranges.length - 1;
@@ -87,7 +77,7 @@ private int[] sortableRange(String cidr) {
8777
rangeStart = 0;
8878
rangeEnd = -1;
8979
} else {
90-
int address = toInt(address(cidr.substring(0, index)));
80+
int address = toInt(IPRanges.address(cidr.substring(0, index)));
9181
int mask = -1 << (32 - maskBits);
9282
rangeStart = address & mask;
9383
rangeEnd = rangeStart + ~mask;
@@ -105,6 +95,7 @@ private int toInt(byte[] address) {
10595
return result;
10696
}
10797

98+
@Override
10899
public boolean matches(byte[] address) {
109100
if (ranges.length == 0) return false;
110101
int sortable = toSortable(toInt(address));
@@ -116,6 +107,7 @@ public boolean matches(byte[] address) {
116107
// 127.255.255.255 => -1 (01111111.11111111.11111111.11111111 => 11111111.11111111.11111111.11111111)
117108
// 128.0.0.0 => 0 (10000000.0.0.0 => 00000000.0.0.0)
118109
// 255.255.255.255 => MAX_VALUE (11111111.11111111.11111111.11111111 => 01111111.11111111.11111111.11111111)
110+
// refer to Integer.compareUnsigned(x, y)
119111
private int toSortable(int value) {
120112
return value ^ Integer.MIN_VALUE;
121113
}

core-ng/src/main/java/core/framework/internal/web/http/IPv6Ranges.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import java.util.Comparator;
55
import java.util.List;
66

7-
public class IPv6Ranges {
7+
public class IPv6Ranges implements IPRanges {
88
static boolean withinRanges(LongLong[] ranges, LongLong value) {
99
int low = 0;
1010
int high = ranges.length - 1;
@@ -89,7 +89,7 @@ public IPv6Ranges(List<String> cidrs) {
8989
private LongLong[] sortableRange(String cidr) {
9090
int index = cidr.indexOf('/');
9191
if (index <= 0 || index >= cidr.length() - 1) throw new Error("invalid cidr, value=" + cidr);
92-
LongLong address = toLongLong(IPv4Ranges.address(cidr.substring(0, index)));
92+
LongLong address = toLongLong(IPRanges.address(cidr.substring(0, index)));
9393
int maskBits = Integer.parseInt(cidr.substring(index + 1));
9494

9595
LongLong rangeStart;
@@ -110,6 +110,7 @@ private LongLong[] sortableRange(String cidr) {
110110
return new LongLong[]{rangeStart.toSortable(), rangeEnd.toSortable()};
111111
}
112112

113+
@Override
113114
public boolean matches(byte[] address) {
114115
if (ranges.length == 0) return false;
115116
if (address.length != 16) return false;

core-ng/src/main/java/core/framework/internal/web/sys/APIController.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import core.framework.internal.web.api.APIDefinitionResponse;
66
import core.framework.internal.web.api.MessageAPIDefinitionBuilder;
77
import core.framework.internal.web.api.MessageAPIDefinitionResponse;
8-
import core.framework.internal.web.http.IPv4AccessControl;
8+
import core.framework.internal.web.http.IPAccessControl;
99
import core.framework.internal.web.service.ErrorResponse;
1010
import core.framework.json.JSON;
1111
import core.framework.web.Request;
@@ -21,7 +21,7 @@
2121
* @author neo
2222
*/
2323
public class APIController {
24-
public final IPv4AccessControl accessControl = new IPv4AccessControl();
24+
public final IPAccessControl accessControl = new IPAccessControl();
2525
private final ReentrantLock lock = new ReentrantLock();
2626

2727
public Set<Class<?>> serviceInterfaces = new LinkedHashSet<>();

core-ng/src/main/java/core/framework/internal/web/sys/CacheController.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import core.framework.http.ContentType;
44
import core.framework.internal.cache.CacheImpl;
5-
import core.framework.internal.web.http.IPv4AccessControl;
5+
import core.framework.internal.web.http.IPAccessControl;
66
import core.framework.json.JSON;
77
import core.framework.util.Strings;
88
import core.framework.web.Request;
@@ -17,7 +17,7 @@
1717
*/
1818
public class CacheController {
1919
private final Map<String, CacheImpl<?>> caches;
20-
private final IPv4AccessControl accessControl = new IPv4AccessControl();
20+
private final IPAccessControl accessControl = new IPAccessControl();
2121

2222
public CacheController(Map<String, CacheImpl<?>> caches) {
2323
this.caches = caches;

core-ng/src/main/java/core/framework/internal/web/sys/DiagnosticController.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import core.framework.http.ContentType;
44
import core.framework.internal.stat.Diagnostic;
5-
import core.framework.internal.web.http.IPv4AccessControl;
5+
import core.framework.internal.web.http.IPAccessControl;
66
import core.framework.util.Files;
77
import core.framework.web.Request;
88
import core.framework.web.Response;
@@ -13,7 +13,7 @@
1313
* @author neo
1414
*/
1515
public class DiagnosticController {
16-
private final IPv4AccessControl accessControl = new IPv4AccessControl();
16+
private final IPAccessControl accessControl = new IPAccessControl();
1717

1818
// add -XX:NativeMemoryTracking=summary or -XX:NativeMemoryTracking=detail to enable native memory tracking, and vmInfo will include NMT summary
1919
// enabling NMT will result in a 5-10 percent JVM performance drop

0 commit comments

Comments
 (0)