Skip to content

Commit 2a61c32

Browse files
committed
Accept IPv6 entries in the usage exemption setting
1 parent 94bca7a commit 2a61c32

7 files changed

Lines changed: 74 additions & 35 deletions

File tree

‎server/src/main/java/com/cloud/configuration/ConfigurationManagerImpl.java‎

Lines changed: 20 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1583,38 +1583,39 @@ protected String validateValueRange(String name, String value, Class<?> type, Co
15831583
}
15841584

15851585
/**
1586-
* Validates configuration values for the given name, value, and type.
1587-
* <ul>
1588-
* <li>The value must be a comma-separated list of key-value pairs, where each value must be a positive integer.</li>
1589-
* <li>Each key-value pair must be in the format "command=value", with the value being a positive integer greater than 0,
1590-
* otherwise fails with an error message</li>
1591-
* <li>Throws an {@link InvalidParameterValueException} if validation fails.</li>
1592-
* </ul>
1593-
*
1594-
* @param name the configuration name
1595-
* @param value the configuration value as a comma-separated string of key-value pairs
1596-
* @param type the configuration type, expected to be String
1597-
* @throws InvalidParameterValueException if validation fails with a specific error message
1598-
*/
1599-
/**
1600-
* Checks that each entry is an IPv4 address or CIDR. We refuse /0 because it would exempt all traffic.
1586+
* Checks that each entry is an IPv4 or IPv6 address or CIDR. We refuse /0 because it would exempt all traffic.
16011587
*/
16021588
protected String validateUsageExemptCidrs(String name, String value) {
16031589
for (String entry : value.split(",")) {
16041590
entry = entry.trim();
1605-
if (entry.isEmpty() || NetUtils.isValidIp4(entry)) {
1591+
if (entry.isEmpty()) {
16061592
continue;
16071593
}
1608-
if (!NetUtils.isValidIp4Cidr(entry)) {
1609-
return String.format("[%s] is not a valid IPv4 address or CIDR for %s.", entry, name);
1594+
final String cidr = VirtualNetworkApplianceManager.normaliseUsageExemptCidr(entry);
1595+
if (cidr == null) {
1596+
return String.format("[%s] is not a valid IP address or CIDR for %s.", entry, name);
16101597
}
1611-
if (NetUtils.getCidr(entry).second() == 0) {
1598+
if (cidr.endsWith("/0")) {
16121599
return String.format("[%s] would exempt all traffic, so it can't be used for %s.", entry, name);
16131600
}
16141601
}
16151602
return null;
16161603
}
16171604

1605+
/**
1606+
* Validates configuration values for the given name, value, and type.
1607+
* <ul>
1608+
* <li>The value must be a comma-separated list of key-value pairs, where each value must be a positive integer.</li>
1609+
* <li>Each key-value pair must be in the format "command=value", with the value being a positive integer greater than 0,
1610+
* otherwise fails with an error message</li>
1611+
* <li>Throws an {@link InvalidParameterValueException} if validation fails.</li>
1612+
* </ul>
1613+
*
1614+
* @param name the configuration name
1615+
* @param value the configuration value as a comma-separated string of key-value pairs
1616+
* @param type the configuration type, expected to be String
1617+
* @throws InvalidParameterValueException if validation fails with a specific error message
1618+
*/
16181619
protected void validateSpecificConfigurationValues(String name, String value, Class<?> type) {
16191620
if (type.equals(String.class)) {
16201621
if (name.equals(AgentManager.GranularWaitTimeForCommands.toString())) {

‎server/src/main/java/com/cloud/network/router/VirtualNetworkApplianceManager.java‎

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020

2121
import org.apache.cloudstack.framework.config.ConfigKey;
2222

23+
import com.googlecode.ipv6.IPv6Network;
24+
2325
import com.cloud.exception.AgentUnavailableException;
2426
import com.cloud.exception.ConcurrentOperationException;
2527
import com.cloud.exception.ResourceUnavailableException;
@@ -29,6 +31,7 @@
2931
import com.cloud.user.Account;
3032
import com.cloud.user.User;
3133
import com.cloud.utils.component.Manager;
34+
import com.cloud.utils.net.NetUtils;
3235
import com.cloud.vm.DomainRouterVO;
3336

3437
/**
@@ -82,10 +85,31 @@ public interface VirtualNetworkApplianceManager extends Manager, VirtualNetworkA
8285
ConfigKey<Boolean> ExposeDnsAndBootpServer = new ConfigKey<>(Boolean.class, "expose.dns.externally", "Advanced", "false",
8386
"open dns, dhcp and bootp on the public interface", true, ConfigKey.Scope.Zone, null);
8487
ConfigKey<String> NetworkUsageExemptCidrs = new ConfigKey<>(String.class, "network.usage.exempt.cidrs", "Usage", "",
85-
"Comma-separated IPv4 addresses or CIDRs. Traffic between guest networks and these addresses doesn't count " +
86-
"towards virtual router network usage. Routers pick up a change the next time they start or restart.",
88+
"Comma-separated IPv4 or IPv6 addresses or CIDRs. Traffic between guest networks and these addresses doesn't count " +
89+
"towards virtual router network usage. Virtual routers only count IPv4 traffic, so they ignore IPv6 entries. " +
90+
"Routers pick up a change the next time they start or restart.",
8791
true, ConfigKey.Scope.Zone, null, null, null, null, null, ConfigKey.Kind.CSV, null);
8892

93+
/**
94+
* Tidies one entry of {@link #NetworkUsageExemptCidrs} into a CIDR, or returns null if it isn't a valid address or CIDR.
95+
* For example, 192.0.2.5 becomes 192.0.2.5/32, 198.51.100.70/24 becomes 198.51.100.0/24 and 2001:db8::5 becomes
96+
* 2001:db8::5/128. Callers refuse a /0 result, as it would exempt all traffic.
97+
*/
98+
static String normaliseUsageExemptCidr(String entry) {
99+
if (NetUtils.isValidIp4(entry)) {
100+
entry = entry + "/32";
101+
} else if (NetUtils.isValidIp6(entry)) {
102+
entry = entry + "/128";
103+
}
104+
if (NetUtils.isValidIp4Cidr(entry)) {
105+
return NetUtils.getCidrSubNet(entry) + "/" + NetUtils.getCidr(entry).second();
106+
}
107+
if (entry.contains(":") && NetUtils.isValidIp6Cidr(entry)) {
108+
return IPv6Network.fromString(entry).toString();
109+
}
110+
return null;
111+
}
112+
89113
ConfigKey<String> VirtualRouterServiceOffering = new ConfigKey<>(String.class, VirtualRouterServiceOfferingCK, "Advanced", "",
90114
"Uuid of the service offering used by virtual routers; if NULL - system offering will be used", true, ConfigKey.Scope.Account, null);
91115

‎server/src/main/java/com/cloud/network/router/VirtualNetworkApplianceManagerImpl.java‎

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3373,8 +3373,9 @@ public String getConfigComponentName() {
33733373
}
33743374

33753375
/**
3376-
* Turns {@link #NetworkUsageExemptCidrs} into the list we pass to the router. For example, 192.0.2.5 becomes
3377-
* 192.0.2.5/32 and 198.51.100.70/24 becomes 198.51.100.0/24. Bad entries and /0 are logged and left out.
3376+
* Turns {@link #NetworkUsageExemptCidrs} into the list we pass to the router, tidied by
3377+
* {@link VirtualNetworkApplianceManager#normaliseUsageExemptCidr}. IPv6 entries are passed on too.
3378+
* The router ignores them for now. Bad entries and /0 are logged and left out.
33783379
*/
33793380
protected List<String> parseUsageExemptCidrs(final String value) {
33803381
final List<String> cidrs = new ArrayList<>();
@@ -3386,14 +3387,11 @@ protected List<String> parseUsageExemptCidrs(final String value) {
33863387
if (entry.isEmpty()) {
33873388
continue;
33883389
}
3389-
if (NetUtils.isValidIp4(entry)) {
3390-
entry = entry + "/32";
3391-
}
3392-
if (!NetUtils.isValidIp4Cidr(entry) || NetUtils.getCidr(entry).second() == 0) {
3393-
logger.warn("Skipping [{}] in {}: not a valid IPv4 address or CIDR, or it's /0", entry, NetworkUsageExemptCidrs.key());
3390+
final String cidr = VirtualNetworkApplianceManager.normaliseUsageExemptCidr(entry);
3391+
if (cidr == null || cidr.endsWith("/0")) {
3392+
logger.warn("Skipping [{}] in {}: not a valid IP address or CIDR, or it's /0", entry, NetworkUsageExemptCidrs.key());
33943393
continue;
33953394
}
3396-
final String cidr = NetUtils.getCidrSubNet(entry) + "/" + NetUtils.getCidr(entry).second();
33973395
if (!cidrs.contains(cidr)) {
33983396
cidrs.add(cidr);
33993397
}

‎server/src/test/java/com/cloud/configuration/ConfigurationManagerImplTest.java‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -870,18 +870,20 @@ private String validateUsageExemptCidrs(String value) {
870870
public void validateValueRangeTestAcceptsUsageExemptAddressesAndCidrs() {
871871
Assert.assertNull(validateUsageExemptCidrs(""));
872872
Assert.assertNull(validateUsageExemptCidrs("192.0.2.5, 198.51.100.70/24,,203.0.113.0/24"));
873+
Assert.assertNull(validateUsageExemptCidrs("2001:db8::5,2001:db8:0:1::7/64"));
873874
}
874875

875876
@Test
876877
public void validateValueRangeTestRejectsInvalidUsageExemptEntry() {
877878
Assert.assertNotNull(validateUsageExemptCidrs("192.0.2.5,bogus"));
878879
Assert.assertNotNull(validateUsageExemptCidrs("192.0.2.300"));
879-
Assert.assertNotNull(validateUsageExemptCidrs("2001:db8::/32"));
880+
Assert.assertNotNull(validateUsageExemptCidrs("2001:db8::/129"));
880881
}
881882

882883
@Test
883884
public void validateValueRangeTestRejectsUsageExemptMatchAll() {
884885
Assert.assertNotNull(validateUsageExemptCidrs("0.0.0.0/0"));
886+
Assert.assertNotNull(validateUsageExemptCidrs("::/0"));
885887
}
886888

887889
@Test

‎server/src/test/java/com/cloud/network/router/VirtualNetworkApplianceManagerImplTest.java‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -428,9 +428,15 @@ public void parseUsageExemptCidrsTestNormalisesEntries() {
428428
Assert.assertEquals(List.of("192.0.2.5/32", "198.51.100.0/24"), cidrs);
429429
}
430430

431+
@Test
432+
public void parseUsageExemptCidrsTestNormalisesIpv6Entries() {
433+
List<String> cidrs = virtualNetworkApplianceManagerImpl.parseUsageExemptCidrs("2001:db8::5, 2001:db8:0:1::7/64,192.0.2.5");
434+
Assert.assertEquals(List.of("2001:db8::5/128", "2001:db8:0:1::/64", "192.0.2.5/32"), cidrs);
435+
}
436+
431437
@Test
432438
public void parseUsageExemptCidrsTestSkipsInvalidEntries() {
433-
List<String> cidrs = virtualNetworkApplianceManagerImpl.parseUsageExemptCidrs("not-an-ip,0.0.0.0/0,10.0.0.0/33,192.168.1.300,172.16.0.0/12");
439+
List<String> cidrs = virtualNetworkApplianceManagerImpl.parseUsageExemptCidrs("not-an-ip,0.0.0.0/0,::/0,10.0.0.0/33,192.168.1.300,172.16.0.0/12");
434440
Assert.assertEquals(List.of("172.16.0.0/12"), cidrs);
435441
}
436442
}

‎systemvm/debian/opt/cloud/bin/cs/CsConfig.py‎

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -116,17 +116,20 @@ def expose_dns(self):
116116
return self.cmdline().idata().get('exposedns', 'false') == 'true'
117117

118118
def get_usage_exempt_cidrs(self):
119-
""" The CIDRs to leave out of network usage, tidied to match how iptables-save prints them.
120-
Without that, each run would see the rules as changed and rewrite them. """
119+
""" The IPv4 CIDRs to leave out of network usage, tidied to match how iptables-save prints them.
120+
Without that, each run would see the rules as changed and rewrite them.
121+
IPv6 entries are skipped, as the router only counts IPv4 traffic. """
121122
cidrs = []
122123
for value in self.cmdline().idata().get('usage_exempt_cidrs', '').split(','):
123124
value = value.strip()
124125
if not value:
125126
continue
126127
try:
127-
network = ipaddress.IPv4Network(value, strict=False)
128+
network = ipaddress.ip_network(value, strict=False)
128129
except ValueError:
129-
logging.warning("Skipping %s in usage exempt CIDRs: not a valid IPv4 address or CIDR", value)
130+
logging.warning("Skipping %s in usage exempt CIDRs: not a valid IP address or CIDR", value)
131+
continue
132+
if network.version != 4:
130133
continue
131134
if network.prefixlen == 0:
132135
logging.warning("Skipping %s in usage exempt CIDRs: it would exempt all traffic", value)

‎systemvm/test/TestCsConfig.py‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,11 @@ def test_usage_exempt_cidrs_skip_invalid_and_match_all(self):
4747
cidrs = self.usage_exempt_cidrs({"usage_exempt_cidrs": "not-an-ip,0.0.0.0/0,192.168.1.300,172.16.0.0/12"})
4848
self.assertEqual(cidrs, ["172.16.0.0/12"])
4949

50+
def test_usage_exempt_cidrs_skip_ipv6(self):
51+
# the router only counts IPv4 traffic, so IPv6 entries have nothing to exempt
52+
cidrs = self.usage_exempt_cidrs({"usage_exempt_cidrs": "2001:db8::5/128,192.0.2.5/32,2001:db8:0:1::/64"})
53+
self.assertEqual(cidrs, ["192.0.2.5/32"])
54+
5055

5156
if __name__ == '__main__':
5257
unittest.main()

0 commit comments

Comments
 (0)