2 * Copyright (c) 2018 Red Hat, Inc. and others. All rights reserved.
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6 * and is available at http://www.eclipse.org/legal/epl-v10.html
8 package org.opendaylight.netvirt.aclservice;
10 import static org.opendaylight.controller.md.sal.binding.api.WriteTransaction.CREATE_MISSING_PARENTS;
11 import static org.opendaylight.genius.infra.Datastore.CONFIGURATION;
13 import java.util.ArrayList;
14 import java.util.Collections;
15 import java.util.List;
17 import java.util.stream.Collectors;
18 import org.opendaylight.controller.md.sal.binding.api.DataBroker;
19 import org.opendaylight.genius.mdsalutil.FlowEntity;
20 import org.opendaylight.genius.mdsalutil.InstructionInfo;
21 import org.opendaylight.genius.mdsalutil.MDSALUtil;
22 import org.opendaylight.genius.mdsalutil.MatchInfoBase;
23 import org.opendaylight.genius.mdsalutil.MetaDataUtil;
24 import org.opendaylight.genius.mdsalutil.NwConstants;
25 import org.opendaylight.genius.mdsalutil.instructions.InstructionGotoTable;
26 import org.opendaylight.genius.mdsalutil.interfaces.IMdsalApiManager;
27 import org.opendaylight.genius.mdsalutil.matches.MatchArpSha;
28 import org.opendaylight.genius.mdsalutil.matches.MatchEthernetSource;
29 import org.opendaylight.genius.mdsalutil.matches.MatchEthernetType;
30 import org.opendaylight.genius.mdsalutil.matches.MatchMetadata;
31 import org.opendaylight.genius.utils.ServiceIndex;
32 import org.opendaylight.infrautils.jobcoordinator.JobCoordinator;
33 import org.opendaylight.netvirt.aclservice.api.AclInterfaceCache;
34 import org.opendaylight.netvirt.aclservice.api.AclServiceManager.Action;
35 import org.opendaylight.netvirt.aclservice.api.AclServiceManager.MatchCriteria;
36 import org.opendaylight.netvirt.aclservice.api.utils.AclInterface;
37 import org.opendaylight.netvirt.aclservice.utils.AclConstants;
38 import org.opendaylight.netvirt.aclservice.utils.AclDataUtil;
39 import org.opendaylight.netvirt.aclservice.utils.AclServiceOFFlowBuilder;
40 import org.opendaylight.netvirt.aclservice.utils.AclServiceUtils;
41 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.types.rev130715.MacAddress;
42 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.instruction.list.Instruction;
43 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.interfacemanager.servicebinding.rev160406.ServiceModeIngress;
44 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.interfacemanager.servicebinding.rev160406.service.bindings.services.info.BoundServices;
45 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.aclservice.rev160608.DirectionBase;
46 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.aclservice.rev160608.DirectionEgress;
47 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.aclservice.rev160608.InterfaceAcl.InterfaceType;
48 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.aclservice.rev160608.IpPrefixOrAddress;
49 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.aclservice.rev160608.interfaces._interface.AllowedAddressPairs;
50 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.aclservice.rev160608.interfaces._interface.SubnetInfo;
51 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
52 import org.opendaylight.yangtools.yang.common.Uint64;
53 import org.slf4j.Logger;
54 import org.slf4j.LoggerFactory;
57 * Provides the implementation for egress (w.r.t VM) ACL service.
60 * Note: Table names used are w.r.t switch. Hence, switch ingress is VM egress
63 public class EgressAclServiceImpl extends AbstractAclServiceImpl {
65 private static final Logger LOG = LoggerFactory.getLogger(EgressAclServiceImpl.class);
68 * Initialize the member variables.
70 public EgressAclServiceImpl(DataBroker dataBroker, IMdsalApiManager mdsalManager, AclDataUtil aclDataUtil,
71 AclServiceUtils aclServiceUtils, JobCoordinator jobCoordinator, AclInterfaceCache aclInterfaceCache) {
72 // Service mode is w.rt. switch
73 super(ServiceModeIngress.class, dataBroker, mdsalManager, aclDataUtil, aclServiceUtils,
74 jobCoordinator, aclInterfaceCache);
80 * @param aclInterface the acl interface
83 public void bindService(AclInterface aclInterface) {
84 String interfaceName = aclInterface.getInterfaceId();
85 LOG.debug("Binding ACL service for interface {}", interfaceName);
86 if (aclInterface.getInterfaceType() == InterfaceType.DhcpService) {
87 LOG.debug("{} is a DHCP serice port. No binding needed", interfaceName);
90 jobCoordinator.enqueueJob(interfaceName, () -> {
91 int instructionKey = 0;
92 List<Instruction> instructions = new ArrayList<>();
93 instructions.add(MDSALUtil.buildAndGetGotoTableInstruction(getAclAntiSpoofingTable(), ++instructionKey));
94 short serviceIndex = ServiceIndex.getIndex(AclConstants.INGRESS_ACL_SERVICE_NAME,
95 AclConstants.INGRESS_ACL_SERVICE_INDEX);
96 int flowPriority = AclConstants.INGRESS_ACL_SERVICE_INDEX;
97 BoundServices serviceInfo =
98 AclServiceUtils.getBoundServices(String.format("%s.%s.%s", "acl", "ingressacl", interfaceName),
99 serviceIndex, flowPriority, AclConstants.COOKIE_ACL_BASE, instructions);
100 InstanceIdentifier<BoundServices> path =
101 AclServiceUtils.buildServiceId(interfaceName, serviceIndex, serviceMode);
103 return Collections.singletonList(
104 txRunner.callWithNewWriteOnlyTransactionAndSubmit(CONFIGURATION, tx -> tx.put(
105 path, serviceInfo, CREATE_MISSING_PARENTS)));
112 * @param aclInterface the acl interface
115 protected void unbindService(AclInterface aclInterface) {
116 String interfaceName = aclInterface.getInterfaceId();
117 InstanceIdentifier<BoundServices> path = AclServiceUtils.buildServiceId(interfaceName,
118 ServiceIndex.getIndex(NwConstants.ACL_SERVICE_NAME, NwConstants.ACL_SERVICE_INDEX), serviceMode);
120 LOG.debug("UnBinding ACL service for interface {}", interfaceName);
121 jobCoordinator.enqueueJob(interfaceName, () -> Collections.singletonList(txRunner
122 .callWithNewWriteOnlyTransactionAndSubmit(CONFIGURATION, tx -> tx.delete(path))));
126 * Programs DHCP Service flows.
128 * @param flowEntries the flow entries
129 * @param port the acl interface
130 * @param action add/modify/remove action
131 * @param addOrRemove addorRemove
134 protected void programDhcpService(List<FlowEntity> flowEntries, AclInterface port,
135 Action action, int addOrRemove) {
136 // No action required on egress.
140 * Programs DHCP service flows.
142 * @param flowEntries the flow entries
143 * @param port the acl interface
144 * @param allowedAddresses the allowed addresses
145 * @param addOrRemove addorRemove
148 protected void processDhcpServiceUpdate(List<FlowEntity> flowEntries, AclInterface port,
149 List<AllowedAddressPairs> allowedAddresses, int addOrRemove) {
150 // No action required on egress.
154 protected void programAntiSpoofingRules(List<FlowEntity> flowEntries, AclInterface port,
155 List<AllowedAddressPairs> allowedAddresses, Action action, int addOrRemove) {
156 LOG.info("{} programAntiSpoofingRules for port {}, AAPs={}, action={}, addOrRemove={}", this.directionString,
157 port.getInterfaceId(), allowedAddresses, action, addOrRemove);
159 Uint64 dpid = Uint64.valueOf(port.getDpId());
160 int lportTag = port.getLPortTag();
161 if (action != Action.UPDATE) {
162 programCommitterDropFlow(flowEntries, dpid, lportTag, addOrRemove);
163 egressAclIcmpv6AllowedList(flowEntries, dpid, lportTag, addOrRemove);
165 List<AllowedAddressPairs> filteredAAPs = AclServiceUtils.excludeMulticastAAPs(allowedAddresses);
166 if (!hasDuplicateMac(port.getAllowedAddressPairs(), filteredAAPs, action)) {
167 programL2BroadcastAllowRule(flowEntries, port, filteredAAPs, addOrRemove);
168 egressAclDhcpAllowClientTraffic(flowEntries, port, filteredAAPs, lportTag, addOrRemove);
169 egressAclDhcpv6AllowClientTraffic(flowEntries, port, filteredAAPs, lportTag, addOrRemove);
171 programArpRule(flowEntries, dpid, filteredAAPs, lportTag, addOrRemove);
174 private void programCommitterDropFlow(List<FlowEntity> flowEntries, Uint64 dpId, int lportTag,
176 List<MatchInfoBase> matches = new ArrayList<>();
177 List<InstructionInfo> instructions = AclServiceOFFlowBuilder.getDropInstructionInfo();
179 Uint64 metaData = Uint64.fromLongBits(MetaDataUtil.getLportTagMetaData(lportTag).longValue()
180 | MetaDataUtil.getAclDropMetaData(AclConstants.METADATA_DROP_FLAG).longValue());
181 Uint64 metaDataMask = Uint64.fromLongBits(MetaDataUtil.METADATA_MASK_LPORT_TAG.longValue()
182 | MetaDataUtil.METADATA_MASK_ACL_DROP.longValue());
183 matches.add(new MatchMetadata(metaData, metaDataMask));
185 String flowName = "Egress_" + dpId + "_" + lportTag + "_Drop";
186 addFlowEntryToList(flowEntries, dpId, getAclCommitterTable(), flowName,
187 AclConstants.CT_STATE_TRACKED_INVALID_PRIORITY, 0, 0, AclServiceUtils.getDropFlowCookie(lportTag),
188 matches, instructions, addOrRemove);
192 protected void programRemoteAclTableFlow(List<FlowEntity> flowEntries, Uint64 dpId, Integer aclTag,
193 AllowedAddressPairs aap, int addOrRemove) {
194 List<MatchInfoBase> flowMatches = new ArrayList<>();
195 flowMatches.addAll(AclServiceUtils.buildIpAndDstServiceMatch(aclTag, aap));
197 List<InstructionInfo> instructions = AclServiceOFFlowBuilder.getGotoInstructionInfo(getAclCommitterTable());
198 String flowNameAdded = "Acl_Filter_Egress_" + aap.getIpAddress().stringValue() + "_" + aclTag;
200 addFlowEntryToList(flowEntries, dpId, getAclRemoteAclTable(), flowNameAdded,
201 AclConstants.ACL_DEFAULT_PRIORITY, 0, 0, AclConstants.COOKIE_ACL_BASE, flowMatches,
202 instructions, addOrRemove);
206 protected void programGotoClassifierTableRules(List<FlowEntity> flowEntries, Uint64 dpId,
207 List<AllowedAddressPairs> aaps, int lportTag, int addOrRemove) {
208 List<AllowedAddressPairs> filteredAAPs = AclServiceUtils.excludeMulticastAAPs(aaps);
209 for (AllowedAddressPairs aap : filteredAAPs) {
210 IpPrefixOrAddress attachIp = aap.getIpAddress();
211 MacAddress mac = aap.getMacAddress();
213 List<MatchInfoBase> matches = new ArrayList<>();
214 matches.add(AclServiceUtils.buildLPortTagMatch(lportTag, serviceMode));
215 matches.add(new MatchEthernetSource(mac));
216 matches.addAll(AclServiceUtils.buildIpMatches(attachIp, MatchCriteria.MATCH_SOURCE));
218 List<InstructionInfo> gotoInstructions = new ArrayList<>();
219 gotoInstructions.add(new InstructionGotoTable(getAclConntrackClassifierTable()));
221 String flowName = "Egress_Fixed_Goto_Classifier_" + dpId + "_" + lportTag + "_"
222 + mac.getValue() + "_" + attachIp.stringValue();
223 addFlowEntryToList(flowEntries, dpId, getAclAntiSpoofingTable(), flowName,
224 AclConstants.PROTO_MATCH_PRIORITY, 0, 0, AclConstants.COOKIE_ACL_BASE, matches, gotoInstructions,
230 * Add rule to allow certain ICMPv6 traffic from VM ports.
232 * @param flowEntries the flow entries
233 * @param dpId the dpId
234 * @param lportTag the lport tag
235 * @param addOrRemove add/remove the flow.
237 private void egressAclIcmpv6AllowedList(List<FlowEntity> flowEntries, Uint64 dpId, int lportTag,
239 List<InstructionInfo> instructions = getDispatcherTableResubmitInstructions();
241 for (Integer icmpv6Type: AclConstants.allowedIcmpv6NdList()) {
242 List<MatchInfoBase> matches = AclServiceUtils.buildIcmpV6Matches(icmpv6Type, 0, lportTag, serviceMode);
243 String flowName = "Egress_ICMPv6" + "_" + dpId + "_" + lportTag + "_" + icmpv6Type + "_Permit_";
244 addFlowEntryToList(flowEntries, dpId, getAclAntiSpoofingTable(), flowName,
245 AclConstants.PROTO_IPV6_ALLOWED_PRIORITY, 0, 0, AclConstants.COOKIE_ACL_BASE, matches,
246 instructions, addOrRemove);
251 * Add rule to ensure only DHCP server traffic from the specified mac is allowed.
253 * @param flowEntries the flow entries
254 * @param port the Acl Interface port
255 * @param allowedAddresses the allowed addresses
256 * @param lportTag the lport tag
257 * @param addOrRemove whether to add or remove the flow
259 private void egressAclDhcpAllowClientTraffic(List<FlowEntity> flowEntries, AclInterface port,
260 List<AllowedAddressPairs> allowedAddresses, int lportTag, int addOrRemove) {
261 Uint64 dpId = Uint64.valueOf(port.getDpId());
262 List<InstructionInfo> instructions = getDispatcherTableResubmitInstructions();
263 for (AllowedAddressPairs aap : allowedAddresses) {
264 if (!AclServiceUtils.isIPv4Address(aap)) {
267 List<MatchInfoBase> matches = new ArrayList<>();
268 matches.addAll(AclServiceUtils.buildDhcpMatches(AclConstants.DHCP_CLIENT_PORT_IPV4,
269 AclConstants.DHCP_SERVER_PORT_IPV4, lportTag, serviceMode));
270 matches.add(new MatchEthernetSource(aap.getMacAddress()));
273 "Egress_DHCP_Client_v4" + dpId + "_" + lportTag + "_" + aap.getMacAddress().getValue() + "_Permit_";
274 addFlowEntryToList(flowEntries, dpId, getAclAntiSpoofingTable(), flowName,
275 AclConstants.PROTO_DHCP_CLIENT_TRAFFIC_MATCH_PRIORITY, 0, 0, AclConstants.COOKIE_ACL_BASE,
276 matches, instructions, addOrRemove);
281 * Add rule to ensure only DHCPv6 server traffic from the specified mac is allowed.
283 * @param flowEntries the flow entries
284 * @param port the Acl Interface port
285 * @param allowedAddresses the allowed addresses
286 * @param lportTag the lport tag
287 * @param addOrRemove whether to add or remove the flow
289 private void egressAclDhcpv6AllowClientTraffic(List<FlowEntity> flowEntries, AclInterface port,
290 List<AllowedAddressPairs> allowedAddresses, int lportTag, int addOrRemove) {
291 Uint64 dpId = Uint64.valueOf(port.getDpId());
292 List<InstructionInfo> instructions = getDispatcherTableResubmitInstructions();
293 for (AllowedAddressPairs aap : allowedAddresses) {
294 if (AclServiceUtils.isIPv4Address(aap)) {
297 List<MatchInfoBase> matches = new ArrayList<>();
298 matches.addAll(AclServiceUtils.buildDhcpV6Matches(AclConstants.DHCP_CLIENT_PORT_IPV6,
299 AclConstants.DHCP_SERVER_PORT_IPV6, lportTag, serviceMode));
300 matches.add(new MatchEthernetSource(aap.getMacAddress()));
302 String flowName = "Egress_DHCP_Client_v6" + "_" + dpId + "_" + lportTag + "_"
303 + aap.getMacAddress().getValue() + "_Permit_";
304 addFlowEntryToList(flowEntries, dpId, getAclAntiSpoofingTable(), flowName,
305 AclConstants.PROTO_DHCP_CLIENT_TRAFFIC_MATCH_PRIORITY, 0, 0, AclConstants.COOKIE_ACL_BASE,
306 matches, instructions, addOrRemove);
311 * Adds the rule to allow arp packets.
313 * @param flowEntries the flow entries
314 * @param dpId the dpId
315 * @param allowedAddresses the allowed addresses
316 * @param lportTag the lport tag
317 * @param addOrRemove whether to add or remove the flow
319 protected void programArpRule(List<FlowEntity> flowEntries, Uint64 dpId,
320 List<AllowedAddressPairs> allowedAddresses, int lportTag, int addOrRemove) {
321 for (AllowedAddressPairs allowedAddress : allowedAddresses) {
322 if (!AclServiceUtils.isIPv4Address(allowedAddress)) {
323 continue; // For IPv6 allowed addresses
326 IpPrefixOrAddress allowedAddressIp = allowedAddress.getIpAddress();
327 MacAddress allowedAddressMac = allowedAddress.getMacAddress();
328 List<MatchInfoBase> arpIpMatches = AclServiceUtils.buildArpIpMatches(allowedAddressIp);
329 List<MatchInfoBase> matches = new ArrayList<>();
330 matches.add(MatchEthernetType.ARP);
331 matches.add(new MatchArpSha(allowedAddressMac));
332 matches.add(new MatchEthernetSource(allowedAddressMac));
333 matches.addAll(arpIpMatches);
334 matches.add(AclServiceUtils.buildLPortTagMatch(lportTag, serviceMode));
336 List<InstructionInfo> instructions = getDispatcherTableResubmitInstructions();
337 LOG.debug("{} ARP Rule on DPID {}, lportTag {}",
338 addOrRemove == NwConstants.DEL_FLOW ? "Deleting" : "Adding", dpId, lportTag);
339 String flowName = "Egress_ARP_" + dpId + "_" + lportTag + "_" + allowedAddress.getMacAddress().getValue()
340 + allowedAddressIp.stringValue();
341 addFlowEntryToList(flowEntries, dpId, getAclAntiSpoofingTable(), flowName,
342 AclConstants.PROTO_ARP_TRAFFIC_MATCH_PRIORITY, 0, 0, AclConstants.COOKIE_ACL_BASE, matches,
343 instructions, addOrRemove);
348 protected void programIcmpv6RARule(List<FlowEntity> flowEntries, AclInterface port, List<SubnetInfo> subnets,
350 // No action required on egress.
354 * Programs broadcast rules.
356 * @param flowEntries the flow entries
357 * @param port the Acl Interface port
358 * @param addOrRemove whether to delete or add flow
361 protected void programBroadcastRules(List<FlowEntity> flowEntries, AclInterface port, Action action,
363 List<AllowedAddressPairs> allowedAddressPairs = port.getAllowedAddressPairs();
364 List<AllowedAddressPairs> filteredAAPs = AclServiceUtils.excludeMulticastAAPs(allowedAddressPairs);
365 if (!hasDuplicateMac(allowedAddressPairs, filteredAAPs, action)) {
366 programL2BroadcastAllowRule(flowEntries, port, filteredAAPs, addOrRemove);
371 * Programs broadcast rules.
373 * @param flowEntries the flow entries
374 * @param port the Acl Interface port
375 * @param subnetInfoList the port subnet info list
376 * @param addOrRemove whether to delete or add flow
378 protected void programSubnetBroadcastRules(List<FlowEntity> flowEntries, AclInterface port,
379 List<SubnetInfo> subnetInfoList, int addOrRemove) {
380 // No action required on egress.
384 * Programs Non-IP broadcast rules.
386 * @param flowEntries the flow entries
387 * @param port the Acl Interface port
388 * @param filteredAAPs the filtered AAPs list
389 * @param addOrRemove whether to delete or add flow
391 private void programL2BroadcastAllowRule(List<FlowEntity> flowEntries, AclInterface port,
392 List<AllowedAddressPairs> filteredAAPs, int addOrRemove) {
393 Uint64 dpId = Uint64.valueOf(port.getDpId());
394 int lportTag = port.getLPortTag();
395 Set<MacAddress> macs = filteredAAPs.stream().map(AllowedAddressPairs::getMacAddress)
396 .collect(Collectors.toSet());
397 for (MacAddress mac : macs) {
398 List<MatchInfoBase> matches = new ArrayList<>();
399 matches.add(new MatchEthernetSource(mac));
400 matches.add(AclServiceUtils.buildLPortTagMatch(lportTag, serviceMode));
402 List<InstructionInfo> instructions = getDispatcherTableResubmitInstructions();
404 String flowName = "Egress_L2Broadcast_" + dpId + "_" + lportTag + "_" + mac.getValue();
405 addFlowEntryToList(flowEntries, dpId, getAclAntiSpoofingTable(), flowName,
406 AclConstants.PROTO_L2BROADCAST_TRAFFIC_MATCH_PRIORITY, 0, 0, AclConstants.COOKIE_ACL_BASE,
407 matches, instructions, addOrRemove);
411 private boolean hasDuplicateMac(List<AllowedAddressPairs> allowedAddresses,
412 List<AllowedAddressPairs> filteredAAPs, Action action) {
413 // Do not proceed further if Port Up OR down event.
414 if (action != Action.UPDATE) {
417 // exclude multicastAAPs from PortBefore/PortAfter AAPs
418 List<AllowedAddressPairs> excludeMulticastAAPs = AclServiceUtils.excludeMulticastAAPs(allowedAddresses);
419 // GET Delta of ADDED/DELETED aaps with PortBefore/PortAfter-AAPs
420 List<AllowedAddressPairs> filteredAllowedAddresses = excludeMulticastAAPs.stream().filter(
421 aap -> !filteredAAPs.contains(aap)).collect(Collectors.toList());
422 Set<MacAddress> macs = filteredAAPs.stream().map(aap -> aap.getMacAddress()).collect(Collectors.toSet());
423 List<AllowedAddressPairs> aapWithDuplicateMac = filteredAllowedAddresses.stream()
424 .filter(entry -> macs.contains(entry.getMacAddress())).collect(Collectors.toList());
426 return !aapWithDuplicateMac.isEmpty();
430 protected boolean isValidDirection(Class<? extends DirectionBase> direction) {
431 return direction.equals(DirectionEgress.class);
434 private short getAclAntiSpoofingTable() {
435 return NwConstants.INGRESS_ACL_ANTI_SPOOFING_TABLE;
438 private short getAclConntrackClassifierTable() {
439 return NwConstants.INGRESS_ACL_CONNTRACK_CLASSIFIER_TABLE;
443 protected short getAclConntrackSenderTable() {
444 return NwConstants.INGRESS_ACL_CONNTRACK_SENDER_TABLE;
448 protected short getAclForExistingTrafficTable() {
449 return NwConstants.INGRESS_ACL_FOR_EXISTING_TRAFFIC_TABLE;
453 protected short getAclFilterCumDispatcherTable() {
454 return NwConstants.INGRESS_ACL_FILTER_CUM_DISPATCHER_TABLE;
458 protected short getAclRuleBasedFilterTable() {
459 return NwConstants.INGRESS_ACL_RULE_BASED_FILTER_TABLE;
463 protected short getAclRemoteAclTable() {
464 return NwConstants.INGRESS_REMOTE_ACL_TABLE;
468 protected short getAclCommitterTable() {
469 return NwConstants.INGRESS_ACL_COMMITTER_TABLE;