Skip to content
This repository was archived by the owner on Mar 5, 2025. It is now read-only.

Commit ce284fd

Browse files
committed
Handle IPv6 addresses in X-Forwarded-For and http-server.internal-networks
1 parent b8ece50 commit ce284fd

File tree

7 files changed

+211
-144
lines changed

7 files changed

+211
-144
lines changed

http-server/src/main/java/com/proofpoint/http/server/CidrSet.java

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717

1818
import com.google.common.collect.ImmutableSet;
1919

20-
import java.net.Inet4Address;
2120
import java.net.InetAddress;
2221
import java.util.Arrays;
2322
import java.util.Collection;
@@ -27,9 +26,9 @@
2726

2827
public class CidrSet
2928
{
30-
private final Set<Inet4Network> cidrs;
29+
private final Set<InetNetwork> cidrs;
3130

32-
private CidrSet(Collection<Inet4Network> cidrs) {
31+
private CidrSet(Collection<InetNetwork> cidrs) {
3332
this.cidrs = Set.copyOf(cidrs);
3433
}
3534

@@ -40,8 +39,8 @@ private CidrSet(Collection<Inet4Network> cidrs) {
4039
* @return A {@link CidrSet} identifying all addresses in the blocks in {@code cidrList}.
4140
*/
4241
public static CidrSet fromString(String cidrList) {
43-
Set<Inet4Network> uris = Arrays.stream(cidrList.split("\\s*,\\s*"))
44-
.map(Inet4Network::fromCidr)
42+
Set<InetNetwork> uris = Arrays.stream(cidrList.split("\\s*,\\s*"))
43+
.map(InetNetwork::fromCidr)
4544
.collect(ImmutableSet.toImmutableSet());
4645
return new CidrSet(uris);
4746
}
@@ -62,13 +61,9 @@ public static CidrSet empty()
6261
*/
6362
public boolean containsAddress(InetAddress address)
6463
{
65-
if (!(address instanceof Inet4Address)) {
66-
return false;
67-
}
68-
69-
Inet4Address inet4Address = (Inet4Address) address;
70-
for (Inet4Network cidr : cidrs) {
71-
if (cidr.containsAddress(inet4Address)) {
64+
InetAddress inetAddress = address;
65+
for (InetNetwork cidr : cidrs) {
66+
if (cidr.containsAddress(inetAddress)) {
7267
return true;
7368
}
7469
}
@@ -77,7 +72,7 @@ public boolean containsAddress(InetAddress address)
7772
}
7873

7974
public CidrSet union(CidrSet other) {
80-
return new CidrSet(ImmutableSet.<Inet4Network>builder()
75+
return new CidrSet(ImmutableSet.<InetNetwork>builder()
8176
.addAll(cidrs)
8277
.addAll(other.cidrs)
8378
.build());
@@ -99,15 +94,14 @@ public boolean equals(Object o)
9994
@Override
10095
public int hashCode()
10196
{
102-
10397
return Objects.hash(cidrs);
10498
}
10599

106100
@Override
107101
public String toString()
108102
{
109103
return cidrs.stream()
110-
.map(Inet4Network::toString)
104+
.map(InetNetwork::toString)
111105
.collect(Collectors.joining(","));
112106
}
113107
}

http-server/src/main/java/com/proofpoint/http/server/ClientAddressExtractor.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import jakarta.servlet.http.HttpServletRequest;
2424

2525
import java.util.Enumeration;
26+
import java.util.stream.StreamSupport;
2627

2728
public class ClientAddressExtractor
2829
{
@@ -52,7 +53,15 @@ public String clientAddressFor(HttpServletRequest request)
5253
ImmutableList.Builder<String> builder = ImmutableList.builder();
5354
for (Enumeration<String> e = request.getHeaders("X-FORWARDED-FOR"); e != null && e.hasMoreElements(); ) {
5455
String forwardedFor = e.nextElement();
55-
builder.addAll(Splitter.on(',').trimResults().omitEmptyStrings().split(forwardedFor));
56+
StreamSupport.stream(Splitter.on(',').trimResults().omitEmptyStrings().split(forwardedFor).spliterator(), false)
57+
.map(s -> {
58+
if (s.startsWith("[") && s.endsWith("]")) {
59+
return s.substring(1, s.length() - 1).trim();
60+
} else {
61+
return s;
62+
}
63+
})
64+
.forEach(builder::add);
5665
}
5766
if (request.getRemoteAddr() != null) {
5867
builder.add(request.getRemoteAddr());

http-server/src/main/java/com/proofpoint/http/server/Inet4Network.java

Lines changed: 0 additions & 107 deletions
This file was deleted.
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
/*
2+
* Copyright 2010 Proofpoint, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.proofpoint.http.server;
17+
18+
import com.google.common.net.InetAddresses;
19+
20+
import java.math.BigInteger;
21+
import java.net.Inet6Address;
22+
import java.net.InetAddress;
23+
import java.util.Objects;
24+
25+
import static com.google.common.net.InetAddresses.toBigInteger;
26+
27+
final class InetNetwork
28+
{
29+
private final boolean isIpv6;
30+
private final int bits;
31+
private final BigInteger start;
32+
private final BigInteger end;
33+
34+
private InetNetwork(InetAddress address, int bits)
35+
{
36+
this.isIpv6 = address instanceof Inet6Address;
37+
this.bits = bits;
38+
this.start = toBigInteger(address);
39+
int totalBits = isIpv6 ? 128 : 32;
40+
BigInteger length = BigInteger.valueOf(1).shiftLeft(totalBits - this.bits);
41+
this.end = start.add(length).subtract(BigInteger.valueOf(1));
42+
}
43+
44+
public boolean containsAddress(InetAddress address)
45+
{
46+
BigInteger ip = addressToBigInteger(address);
47+
if ((address instanceof Inet6Address) != isIpv6) {
48+
return false;
49+
}
50+
return (ip.compareTo(start) >= 0) && (ip.compareTo(end) <= 0);
51+
}
52+
53+
@Override
54+
public String toString()
55+
{
56+
if (isIpv6) {
57+
return InetAddresses.toAddrString(InetAddresses.fromIPv6BigInteger(start)) + "/" + bits;
58+
}
59+
return InetAddresses.fromIPv4BigInteger(start).getHostAddress() + "/" + bits;
60+
}
61+
62+
@Override
63+
public boolean equals(Object o)
64+
{
65+
if (o == null || getClass() != o.getClass()) {
66+
return false;
67+
}
68+
InetNetwork that = (InetNetwork) o;
69+
return isIpv6 == that.isIpv6 && bits == that.bits && Objects.equals(start, that.start) && Objects.equals(end, that.end);
70+
}
71+
72+
@Override
73+
public int hashCode()
74+
{
75+
return Objects.hash(isIpv6, bits, start, end);
76+
}
77+
78+
@SuppressWarnings("StringSplitter")
79+
public static InetNetwork fromCidr(String cidr)
80+
{
81+
String[] parts = cidr.split("/");
82+
if (parts.length != 2) {
83+
throw new IllegalArgumentException("invalid CIDR format: " + cidr);
84+
}
85+
86+
InetAddress address = InetAddresses.forString(parts[0]);
87+
int bits = Integer.parseInt(parts[1]);
88+
int totalBits = address instanceof Inet6Address ? 128 : 32;
89+
if ((bits < 0) || (bits > totalBits)) {
90+
throw new IllegalArgumentException("invalid prefix size: " + bits);
91+
}
92+
93+
BigInteger mask = (bits == 0) ? BigInteger.ZERO : (BigInteger.valueOf(-1).shiftLeft(totalBits - bits));
94+
BigInteger ip = InetAddresses.toBigInteger(address);
95+
if (!ip.and(mask).equals(ip)) {
96+
throw new IllegalArgumentException("invalid prefix for prefix size: " + bits);
97+
}
98+
99+
return new InetNetwork(address, bits);
100+
}
101+
102+
static BigInteger addressToBigInteger(InetAddress address)
103+
{
104+
return toBigInteger(address);
105+
}
106+
}

http-server/src/test/java/com/proofpoint/http/server/TestClientAddressExtractor.java

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929

3030
public class TestClientAddressExtractor
3131
{
32+
public static final InternalNetworkConfig INTERNAL_NETWORK_CONFIG = new InternalNetworkConfig().setInternalNetworks(CidrSet.fromString("2001:db8::/64"));
3233
Request request;
3334

3435
@BeforeMethod
@@ -55,6 +56,15 @@ public void testUseForwardedFor()
5556
assertEquals(new ClientAddressExtractor().clientAddressFor(request), "4.4.4.4");
5657
}
5758

59+
@Test
60+
public void testUseForwardedForIPv6()
61+
{
62+
when(request.getRemoteAddr()).thenReturn("2001:db8::1");
63+
when(request.getHeaders("X-FORWARDED-FOR")).thenReturn(Collections.enumeration(List.of("1.1.1.1, 2.2.2.2", "3.3.3.3, 4.4.4.4")));
64+
65+
assertEquals(new ClientAddressExtractor(INTERNAL_NETWORK_CONFIG).clientAddressFor(request), "4.4.4.4");
66+
}
67+
5868
@Test
5969
public void testUseForwardedForTwoHops()
6070
{
@@ -64,6 +74,42 @@ public void testUseForwardedForTwoHops()
6474
assertEquals(new ClientAddressExtractor().clientAddressFor(request), "3.3.3.3");
6575
}
6676

77+
@Test
78+
public void testUseForwardedForTwoHopsIPv6ToIPv4()
79+
{
80+
when(request.getRemoteAddr()).thenReturn("2001:db8::1");
81+
when(request.getHeaders("X-FORWARDED-FOR")).thenReturn(Collections.enumeration(List.of("1.1.1.1, 2.2.2.2", "3.3.3.3, 2001:db8::2")));
82+
83+
assertEquals(new ClientAddressExtractor(INTERNAL_NETWORK_CONFIG).clientAddressFor(request), "3.3.3.3");
84+
}
85+
86+
@Test
87+
public void testUseForwardedForTwoHopsBracketedIPv6ToIPv4()
88+
{
89+
when(request.getRemoteAddr()).thenReturn("2001:db8::1");
90+
when(request.getHeaders("X-FORWARDED-FOR")).thenReturn(Collections.enumeration(List.of("1.1.1.1, 2.2.2.2", "3.3.3.3, [2001:db8::2]")));
91+
92+
assertEquals(new ClientAddressExtractor(INTERNAL_NETWORK_CONFIG).clientAddressFor(request), "3.3.3.3");
93+
}
94+
95+
@Test
96+
public void testUseForwardedForTwoHopsIPv6toIPv6()
97+
{
98+
when(request.getRemoteAddr()).thenReturn("2001:db8::1");
99+
when(request.getHeaders("X-FORWARDED-FOR")).thenReturn(Collections.enumeration(List.of("1.1.1.1, 2.2.2.2", "2001:db8:3::3, 2001:db8::2")));
100+
101+
assertEquals(new ClientAddressExtractor(INTERNAL_NETWORK_CONFIG).clientAddressFor(request), "2001:db8:3::3");
102+
}
103+
104+
@Test
105+
public void testUseForwardedForTwoHopsBracketedIPv6toIPv6()
106+
{
107+
when(request.getRemoteAddr()).thenReturn("2001:db8::1");
108+
when(request.getHeaders("X-FORWARDED-FOR")).thenReturn(Collections.enumeration(List.of("1.1.1.1, 2.2.2.2", "[2001:db8:3::3], [2001:db8::2]")));
109+
110+
assertEquals(new ClientAddressExtractor(INTERNAL_NETWORK_CONFIG).clientAddressFor(request), "2001:db8:3::3");
111+
}
112+
67113
@Test
68114
public void testUseForwardedForThreeHops()
69115
{

0 commit comments

Comments
 (0)