Skip to content

Commit b25c728

Browse files
Keep protocol 6 and 17 without ports as the whole protocol
Sending 6 or 17 to the VR as tcp or udp also when the rule has no ports made it a tcp or udp rule with the port range 0:0, which the VR's iptables path applies as --dport 0: a rule meant for all of TCP or UDP matched port 0 only. Map them only when the rule has ports; without, the bare number is what matches the whole protocol, as before. Also read the protocol as a number, so that "006" is treated as 6, as the API's own validation does, and check the ports a rule already carries when an update changes its protocol, so that switching a tcp rule to another protocol number cannot leave its ports on it. Signed-off-by: Brad House <bhouse@nexthop.ai>
1 parent 59da02e commit b25c728

4 files changed

Lines changed: 77 additions & 19 deletions

File tree

‎core/src/main/java/com/cloud/agent/api/routing/SetNetworkACLCommand.java‎

Lines changed: 29 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,16 @@
2525
import java.util.List;
2626
import java.util.Locale;
2727

28+
import org.apache.commons.lang3.StringUtils;
29+
2830
import com.cloud.agent.api.to.NetworkACLTO;
2931
import com.cloud.agent.api.to.NicTO;
3032
import com.cloud.utils.net.NetUtils;
3133

3234
public class SetNetworkACLCommand extends NetworkElementCommand {
3335
public static final String RULE_DETAIL_SEPARATOR = ";";
36+
private static final int TCP_PROTO_NUMBER = 6;
37+
private static final int UDP_PROTO_NUMBER = 17;
3438

3539
NetworkACLTO[] rules;
3640
NicTO nic;
@@ -71,7 +75,7 @@ public String[][] generateFwRules() {
7175

7276
List<String> cidr;
7377
final StringBuilder sb = new StringBuilder();
74-
final String protocol = normalizeProtocol(aclTO.getProtocol());
78+
final String protocol = normalizeProtocol(aclTO.getProtocol(), aclTO.getSrcPortRange() != null);
7579
sb.append(aclTO.getTrafficType().toString()).append(RULE_DETAIL_SEPARATOR).append(protocol).append(RULE_DETAIL_SEPARATOR);
7680
if (NetUtils.ICMP_PROTO.equals(protocol)) {
7781
sb.append(icmpTypeOrCode(aclTO.getIcmpType())).append(RULE_DETAIL_SEPARATOR).append(icmpTypeOrCode(aclTO.getIcmpCode())).append(RULE_DETAIL_SEPARATOR);
@@ -100,26 +104,36 @@ public String[][] generateFwRules() {
100104
}
101105

102106
/**
103-
* The VR applies ports only to a rule it is told is tcp or udp, and an ICMP type and code only to
104-
* one it is told is icmp; any other protocol goes to it as a bare number. Name those three by their
105-
* protocol number too, and whatever the case they were stored in, so their ports or ICMP type are
106-
* not dropped.
107+
* The VR applies ports only to a rule it is sent as tcp or udp, and an ICMP type and code only to one
108+
* sent as icmp; any other protocol goes to it as a bare number, which matches the whole protocol. So
109+
* send protocol number 1 as icmp, and 6 and 17 as tcp and udp when the rule has ports, whatever form
110+
* or case they were stored in. Without ports 6 and 17 stay bare numbers: that is what matches all of
111+
* TCP or UDP, where a tcp or udp rule without ports would match port 0 only.
107112
*/
108-
protected static String normalizeProtocol(String protocol) {
113+
protected static String normalizeProtocol(String protocol, boolean hasPorts) {
109114
if (protocol == null) {
110115
return null;
111116
}
112117
String p = protocol.trim().toLowerCase(Locale.ROOT);
113-
switch (p) {
114-
case "1":
115-
return NetUtils.ICMP_PROTO;
116-
case "6":
117-
return NetUtils.TCP_PROTO;
118-
case "17":
119-
return NetUtils.UDP_PROTO;
120-
default:
121-
return p;
118+
if (!StringUtils.isNumeric(p)) {
119+
return p;
120+
}
121+
int number;
122+
try {
123+
number = Integer.parseInt(p);
124+
} catch (NumberFormatException e) {
125+
return p;
126+
}
127+
if (number == NetUtils.ICMP_PROTO_NUMBER) {
128+
return NetUtils.ICMP_PROTO;
129+
}
130+
if (hasPorts && number == TCP_PROTO_NUMBER) {
131+
return NetUtils.TCP_PROTO;
132+
}
133+
if (hasPorts && number == UDP_PROTO_NUMBER) {
134+
return NetUtils.UDP_PROTO;
122135
}
136+
return String.valueOf(number);
123137
}
124138

125139
private static int icmpTypeOrCode(Integer value) {

‎core/src/test/java/com/cloud/agent/api/routing/SetNetworkACLCommandTest.java‎

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,20 @@ public void testTcpAndUdpByProtocolNumberKeepTheirPorts() {
6464
assertEquals("Ingress;udp;53;53;10.0.0.0/24;ACCEPT;", generateRule("17", 53, 53, null, null));
6565
}
6666

67+
@Test
68+
public void testTcpAndUdpByProtocolNumberWithoutPortsStayWholeProtocol() {
69+
// as tcp or udp with no ports the VR would match port 0 only
70+
assertEquals("Ingress;6;0;0;10.0.0.0/24;ACCEPT;", generateRule("6", null, null, null, null));
71+
assertEquals("Ingress;17;0;0;10.0.0.0/24;ACCEPT;", generateRule("17", null, null, null, null));
72+
}
73+
74+
@Test
75+
public void testProtocolNumberInAnotherFormIsReadAsANumber() {
76+
assertEquals("Ingress;tcp;22;22;10.0.0.0/24;ACCEPT;", generateRule("006", 22, 22, null, null));
77+
assertEquals("Ingress;icmp;8;0;10.0.0.0/24;ACCEPT;", generateRule(" 01 ", null, null, 8, 0));
78+
assertEquals("Ingress;47;0;0;10.0.0.0/24;ACCEPT;", generateRule("047", null, null, null, null));
79+
}
80+
6781
@Test
6882
public void testIcmpByProtocolNumberKeepsItsTypeAndCode() {
6983
assertEquals("Ingress;icmp;8;0;10.0.0.0/24;ACCEPT;", generateRule("1", null, null, 8, 0));
@@ -72,9 +86,8 @@ public void testIcmpByProtocolNumberKeepsItsTypeAndCode() {
7286
}
7387

7488
@Test
75-
public void testProtocolNameIsMatchedWhateverItsCase() {
89+
public void testIcmpIsMatchedWhateverItsCase() {
7690
assertEquals("Ingress;icmp;8;0;10.0.0.0/24;ACCEPT;", generateRule("ICMP", null, null, 8, 0));
77-
assertEquals("Ingress;tcp;22;22;10.0.0.0/24;ACCEPT;", generateRule("TCP", 22, 22, null, null));
7891
}
7992

8093
@Test

‎server/src/main/java/com/cloud/network/vpc/NetworkACLServiceImpl.java‎

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -709,6 +709,18 @@ protected void validatePortsAreUsableWithProtocol(String protocol, Integer sourc
709709
}
710710
}
711711

712+
/**
713+
* On update the ports a rule already carries are only checked when the protocol is given too, so
714+
* that a rule stored with ports on another protocol number can still be edited otherwise, while a
715+
* protocol change cannot leave them on it.
716+
*/
717+
protected void validatePortsAreUsableWithProtocolOnUpdate(UpdateNetworkACLItemCmd updateNetworkACLItemCmd, NetworkACLItemVO networkACLItemVo) {
718+
boolean protocolGiven = StringUtils.isNotBlank(updateNetworkACLItemCmd.getProtocol());
719+
validatePortsAreUsableWithProtocol(networkACLItemVo.getProtocol(),
720+
protocolGiven ? networkACLItemVo.getSourcePortStart() : updateNetworkACLItemCmd.getSourcePortStart(),
721+
protocolGiven ? networkACLItemVo.getSourcePortEnd() : updateNetworkACLItemCmd.getSourcePortEnd());
722+
}
723+
712724
@Override
713725
public NetworkACLItem getNetworkACLItem(final long ruleId) {
714726
return _networkAclMgr.getNetworkACLItem(ruleId);
@@ -895,8 +907,7 @@ public NetworkACLItem updateNetworkACLItem(UpdateNetworkACLItemCmd updateNetwork
895907

896908
transferDataToNetworkAclRulePojo(updateNetworkACLItemCmd, networkACLItemVo, acl);
897909
validateNetworkACLItem(networkACLItemVo);
898-
// only the ports given now: older rules may already carry ports, and must stay editable
899-
validatePortsAreUsableWithProtocol(networkACLItemVo.getProtocol(), updateNetworkACLItemCmd.getSourcePortStart(), updateNetworkACLItemCmd.getSourcePortEnd());
910+
validatePortsAreUsableWithProtocolOnUpdate(updateNetworkACLItemCmd, networkACLItemVo);
900911
return _networkAclMgr.updateNetworkACLItem(networkACLItemVo);
901912
}
902913

‎server/src/test/java/com/cloud/network/vpc/NetworkACLServiceImplTest.java‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -564,6 +564,26 @@ public void validatePortsAreUsableWithProtocolTestOtherProtocolNumberWithStartPo
564564
networkAclServiceImpl.validatePortsAreUsableWithProtocol("47", 22, null);
565565
}
566566

567+
@Test
568+
public void validatePortsAreUsableWithProtocolOnUpdateTestStoredPortsKeptWhenProtocolNotGiven() {
569+
Mockito.when(updateNetworkACLItemCmdMock.getProtocol()).thenReturn(null);
570+
Mockito.when(updateNetworkACLItemCmdMock.getSourcePortStart()).thenReturn(null);
571+
Mockito.when(updateNetworkACLItemCmdMock.getSourcePortEnd()).thenReturn(null);
572+
Mockito.when(networkAclItemVoMock.getProtocol()).thenReturn("47");
573+
574+
networkAclServiceImpl.validatePortsAreUsableWithProtocolOnUpdate(updateNetworkACLItemCmdMock, networkAclItemVoMock);
575+
}
576+
577+
@Test(expected = InvalidParameterValueException.class)
578+
public void validatePortsAreUsableWithProtocolOnUpdateTestProtocolChangedOntoStoredPorts() {
579+
Mockito.when(updateNetworkACLItemCmdMock.getProtocol()).thenReturn("47");
580+
Mockito.when(networkAclItemVoMock.getProtocol()).thenReturn("47");
581+
Mockito.when(networkAclItemVoMock.getSourcePortStart()).thenReturn(22);
582+
Mockito.when(networkAclItemVoMock.getSourcePortEnd()).thenReturn(22);
583+
584+
networkAclServiceImpl.validatePortsAreUsableWithProtocolOnUpdate(updateNetworkACLItemCmdMock, networkAclItemVoMock);
585+
}
586+
567587
@Test
568588
public void validateSourceCidrListTestEmptySourceCirdList() {
569589
Mockito.when(networkAclItemVoMock.getSourceCidrList()).thenReturn(new ArrayList<>());

0 commit comments

Comments
 (0)