a80e61c2f5a4684e08d7f19101ed1ef93b30bfed
[netvirt.git] / aclservice / impl / src / main / java / org / opendaylight / netvirt / aclservice / EgressAclServiceImpl.java
1 /*
2  * Copyright (c) 2018 Red Hat, Inc. and others. All rights reserved.
3  *
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
7  */
8 package org.opendaylight.netvirt.aclservice;
9
10 import static org.opendaylight.controller.md.sal.binding.api.WriteTransaction.CREATE_MISSING_PARENTS;
11 import static org.opendaylight.genius.infra.Datastore.CONFIGURATION;
12
13 import java.math.BigInteger;
14 import java.util.ArrayList;
15 import java.util.Collections;
16 import java.util.List;
17 import java.util.Set;
18 import java.util.stream.Collectors;
19 import org.opendaylight.controller.md.sal.binding.api.DataBroker;
20 import org.opendaylight.genius.mdsalutil.FlowEntity;
21 import org.opendaylight.genius.mdsalutil.InstructionInfo;
22 import org.opendaylight.genius.mdsalutil.MDSALUtil;
23 import org.opendaylight.genius.mdsalutil.MatchInfoBase;
24 import org.opendaylight.genius.mdsalutil.MetaDataUtil;
25 import org.opendaylight.genius.mdsalutil.NwConstants;
26 import org.opendaylight.genius.mdsalutil.instructions.InstructionGotoTable;
27 import org.opendaylight.genius.mdsalutil.interfaces.IMdsalApiManager;
28 import org.opendaylight.genius.mdsalutil.matches.MatchArpSha;
29 import org.opendaylight.genius.mdsalutil.matches.MatchEthernetSource;
30 import org.opendaylight.genius.mdsalutil.matches.MatchEthernetType;
31 import org.opendaylight.genius.mdsalutil.matches.MatchMetadata;
32 import org.opendaylight.genius.utils.ServiceIndex;
33 import org.opendaylight.infrautils.jobcoordinator.JobCoordinator;
34 import org.opendaylight.netvirt.aclservice.api.AclInterfaceCache;
35 import org.opendaylight.netvirt.aclservice.api.AclServiceManager.Action;
36 import org.opendaylight.netvirt.aclservice.api.AclServiceManager.MatchCriteria;
37 import org.opendaylight.netvirt.aclservice.api.utils.AclInterface;
38 import org.opendaylight.netvirt.aclservice.utils.AclConstants;
39 import org.opendaylight.netvirt.aclservice.utils.AclDataUtil;
40 import org.opendaylight.netvirt.aclservice.utils.AclServiceOFFlowBuilder;
41 import org.opendaylight.netvirt.aclservice.utils.AclServiceUtils;
42 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.yang.types.rev130715.MacAddress;
43 import org.opendaylight.yang.gen.v1.urn.opendaylight.flow.types.rev131026.instruction.list.Instruction;
44 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.interfacemanager.servicebinding.rev160406.ServiceModeIngress;
45 import org.opendaylight.yang.gen.v1.urn.opendaylight.genius.interfacemanager.servicebinding.rev160406.service.bindings.services.info.BoundServices;
46 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.aclservice.rev160608.DirectionBase;
47 import org.opendaylight.yang.gen.v1.urn.opendaylight.netvirt.aclservice.rev160608.DirectionEgress;
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.port.subnets.port.subnet.SubnetInfo;
51 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
52 import org.slf4j.Logger;
53 import org.slf4j.LoggerFactory;
54
55 /**
56  * Provides the implementation for egress (w.r.t VM) ACL service.
57  *
58  * <p>
59  * Note: Table names used are w.r.t switch. Hence, switch ingress is VM egress
60  * and vice versa.
61  */
62 public class EgressAclServiceImpl extends AbstractAclServiceImpl {
63
64     private static final Logger LOG = LoggerFactory.getLogger(EgressAclServiceImpl.class);
65
66     /**
67      * Initialize the member variables.
68      */
69     public EgressAclServiceImpl(DataBroker dataBroker, IMdsalApiManager mdsalManager, AclDataUtil aclDataUtil,
70             AclServiceUtils aclServiceUtils, JobCoordinator jobCoordinator, AclInterfaceCache aclInterfaceCache) {
71         // Service mode is w.rt. switch
72         super(ServiceModeIngress.class, dataBroker, mdsalManager, aclDataUtil, aclServiceUtils,
73                 jobCoordinator, aclInterfaceCache);
74     }
75
76     /**
77      * Bind service.
78      *
79      * @param aclInterface the acl interface
80      */
81     @Override
82     public void bindService(AclInterface aclInterface) {
83         String interfaceName = aclInterface.getInterfaceId();
84         jobCoordinator.enqueueJob(interfaceName, () -> {
85             int instructionKey = 0;
86             List<Instruction> instructions = new ArrayList<>();
87             instructions.add(MDSALUtil.buildAndGetGotoTableInstruction(getAclAntiSpoofingTable(), ++instructionKey));
88             short serviceIndex = ServiceIndex.getIndex(AclConstants.INGRESS_ACL_SERVICE_NAME,
89                     AclConstants.INGRESS_ACL_SERVICE_INDEX);
90             int flowPriority = AclConstants.INGRESS_ACL_SERVICE_INDEX;
91             BoundServices serviceInfo =
92                     AclServiceUtils.getBoundServices(String.format("%s.%s.%s", "acl", "ingressacl", interfaceName),
93                             serviceIndex, flowPriority, AclConstants.COOKIE_ACL_BASE, instructions);
94             InstanceIdentifier<BoundServices> path =
95                     AclServiceUtils.buildServiceId(interfaceName, serviceIndex, serviceMode);
96
97             return Collections.singletonList(
98                     txRunner.callWithNewWriteOnlyTransactionAndSubmit(CONFIGURATION, tx -> tx.put(
99                             path, serviceInfo, CREATE_MISSING_PARENTS)));
100         });
101     }
102
103     /**
104      * Unbind service.
105      *
106      * @param aclInterface the acl interface
107      */
108     @Override
109     protected void unbindService(AclInterface aclInterface) {
110         String interfaceName = aclInterface.getInterfaceId();
111         InstanceIdentifier<BoundServices> path = AclServiceUtils.buildServiceId(interfaceName,
112                 ServiceIndex.getIndex(NwConstants.ACL_SERVICE_NAME, NwConstants.ACL_SERVICE_INDEX), serviceMode);
113
114         LOG.debug("UnBinding ACL service for interface {}", interfaceName);
115         jobCoordinator.enqueueJob(interfaceName, () -> Collections.singletonList(txRunner
116                 .callWithNewWriteOnlyTransactionAndSubmit(CONFIGURATION, tx -> tx.delete(path))));
117     }
118
119     @Override
120     protected void programAntiSpoofingRules(List<FlowEntity> flowEntries, AclInterface port,
121             List<AllowedAddressPairs> allowedAddresses, Action action, int addOrRemove) {
122         LOG.info("{} programAntiSpoofingRules for port {}, AAPs={}, action={}, addOrRemove={}", this.directionString,
123                 port.getInterfaceId(), allowedAddresses, action, addOrRemove);
124
125         BigInteger dpid = port.getDpId();
126         int lportTag = port.getLPortTag();
127         if (action != Action.UPDATE) {
128             programCommitterDropFlow(flowEntries, dpid, lportTag, addOrRemove);
129             egressAclIcmpv6AllowedList(flowEntries, dpid, lportTag, addOrRemove);
130         }
131         List<AllowedAddressPairs> filteredAAPs = AclServiceUtils.excludeMulticastAAPs(allowedAddresses);
132         programL2BroadcastAllowRule(flowEntries, port, filteredAAPs, addOrRemove);
133
134         egressAclDhcpAllowClientTraffic(flowEntries, port, filteredAAPs, lportTag, addOrRemove);
135         egressAclDhcpv6AllowClientTraffic(flowEntries, port, filteredAAPs, lportTag, addOrRemove);
136         programArpRule(flowEntries, dpid, filteredAAPs, lportTag, addOrRemove);
137     }
138
139     private void programCommitterDropFlow(List<FlowEntity> flowEntries, BigInteger dpId, int lportTag,
140             int addOrRemove) {
141         List<MatchInfoBase> matches = new ArrayList<>();
142         List<InstructionInfo> instructions = AclServiceOFFlowBuilder.getDropInstructionInfo();
143
144         BigInteger metaData = MetaDataUtil.getLportTagMetaData(lportTag)
145                 .or(MetaDataUtil.getAclDropMetaData(AclConstants.METADATA_DROP_FLAG));
146         BigInteger metaDataMask =
147                 MetaDataUtil.METADATA_MASK_LPORT_TAG.or(MetaDataUtil.METADATA_MASK_ACL_DROP);
148         matches.add(new MatchMetadata(metaData, metaDataMask));
149
150         String flowName = "Egress_" + dpId + "_" + lportTag + "_Drop";
151         addFlowEntryToList(flowEntries, dpId, getAclCommitterTable(), flowName,
152                 AclConstants.CT_STATE_TRACKED_INVALID_PRIORITY, 0, 0, AclServiceUtils.getDropFlowCookie(lportTag),
153                 matches, instructions, addOrRemove);
154     }
155
156     @Override
157     protected void programRemoteAclTableFlow(List<FlowEntity> flowEntries, BigInteger dpId, Integer aclTag,
158             AllowedAddressPairs aap, int addOrRemove) {
159         List<MatchInfoBase> flowMatches = new ArrayList<>();
160         flowMatches.addAll(AclServiceUtils.buildIpAndDstServiceMatch(aclTag, aap));
161
162         List<InstructionInfo> instructions = AclServiceOFFlowBuilder.getGotoInstructionInfo(getAclCommitterTable());
163         String flowNameAdded = "Acl_Filter_Egress_" + aap.getIpAddress().stringValue() + "_" + aclTag;
164
165         addFlowEntryToList(flowEntries, dpId, getAclRemoteAclTable(), flowNameAdded, AclConstants.ACL_DEFAULT_PRIORITY,
166                 0, 0, AclConstants.COOKIE_ACL_BASE, flowMatches, instructions, addOrRemove);
167     }
168
169     @Override
170     protected void programGotoClassifierTableRules(List<FlowEntity> flowEntries, BigInteger dpId,
171             List<AllowedAddressPairs> aaps, int lportTag, int addOrRemove) {
172         List<AllowedAddressPairs> filteredAAPs = AclServiceUtils.excludeMulticastAAPs(aaps);
173         for (AllowedAddressPairs aap : filteredAAPs) {
174             IpPrefixOrAddress attachIp = aap.getIpAddress();
175             MacAddress mac = aap.getMacAddress();
176
177             List<MatchInfoBase> matches = new ArrayList<>();
178             matches.add(AclServiceUtils.buildLPortTagMatch(lportTag, serviceMode));
179             matches.add(new MatchEthernetSource(mac));
180             matches.addAll(AclServiceUtils.buildIpMatches(attachIp, MatchCriteria.MATCH_SOURCE));
181
182             List<InstructionInfo> gotoInstructions = new ArrayList<>();
183             gotoInstructions.add(new InstructionGotoTable(getAclConntrackClassifierTable()));
184
185             String flowName = "Egress_Fixed_Goto_Classifier_" + dpId + "_" + lportTag + "_" + mac.getValue() + "_"
186                     + attachIp.stringValue();
187             addFlowEntryToList(flowEntries, dpId, getAclAntiSpoofingTable(), flowName,
188                     AclConstants.PROTO_MATCH_PRIORITY, 0, 0, AclConstants.COOKIE_ACL_BASE, matches, gotoInstructions,
189                     addOrRemove);
190         }
191     }
192
193     /**
194      * Add rule to allow certain ICMPv6 traffic from VM ports.
195      *
196      * @param flowEntries the flow entries
197      * @param dpId the dpId
198      * @param lportTag the lport tag
199      * @param addOrRemove add/remove the flow.
200      */
201     private void egressAclIcmpv6AllowedList(List<FlowEntity> flowEntries, BigInteger dpId, int lportTag,
202             int addOrRemove) {
203         List<InstructionInfo> instructions = getDispatcherTableResubmitInstructions();
204
205         for (Integer icmpv6Type: AclConstants.allowedIcmpv6NdList()) {
206             List<MatchInfoBase> matches = AclServiceUtils.buildIcmpV6Matches(icmpv6Type, 0, lportTag, serviceMode);
207             String flowName = "Egress_ICMPv6" + "_" + dpId + "_" + lportTag + "_" + icmpv6Type + "_Permit_";
208             addFlowEntryToList(flowEntries, dpId, getAclAntiSpoofingTable(), flowName,
209                     AclConstants.PROTO_IPV6_ALLOWED_PRIORITY, 0, 0, AclConstants.COOKIE_ACL_BASE, matches,
210                     instructions, addOrRemove);
211         }
212     }
213
214     /**
215      * Add rule to ensure only DHCP server traffic from the specified mac is allowed.
216      *
217      * @param flowEntries the flow entries
218      * @param port the Acl Interface port
219      * @param allowedAddresses the allowed addresses
220      * @param lportTag the lport tag
221      * @param addOrRemove whether to add or remove the flow
222      */
223     private void egressAclDhcpAllowClientTraffic(List<FlowEntity> flowEntries, AclInterface port,
224             List<AllowedAddressPairs> allowedAddresses, int lportTag, int addOrRemove) {
225         // if there is a duplicate mac with different aap, do not delete the Dhcp Allow rule.
226         if (hasDuplicateMac(port.getAllowedAddressPairs(), allowedAddresses)) {
227             return;
228         }
229         BigInteger dpId = port.getDpId();
230         List<InstructionInfo> instructions = getDispatcherTableResubmitInstructions();
231         for (AllowedAddressPairs aap : allowedAddresses) {
232             if (!AclServiceUtils.isIPv4Address(aap)) {
233                 continue;
234             }
235             List<MatchInfoBase> matches = new ArrayList<>();
236             matches.addAll(AclServiceUtils.buildDhcpMatches(AclConstants.DHCP_CLIENT_PORT_IPV4,
237                 AclConstants.DHCP_SERVER_PORT_IPV4, lportTag, serviceMode));
238             matches.add(new MatchEthernetSource(aap.getMacAddress()));
239
240             String flowName =
241                     "Egress_DHCP_Client_v4" + dpId + "_" + lportTag + "_" + aap.getMacAddress().getValue() + "_Permit_";
242             addFlowEntryToList(flowEntries, dpId, getAclAntiSpoofingTable(), flowName,
243                     AclConstants.PROTO_DHCP_CLIENT_TRAFFIC_MATCH_PRIORITY, 0, 0, AclConstants.COOKIE_ACL_BASE,
244                     matches, instructions, addOrRemove);
245         }
246     }
247
248     /**
249      * Add rule to ensure only DHCPv6 server traffic from the specified mac is allowed.
250      *
251      * @param flowEntries the flow entries
252      * @param port the Acl Interface port
253      * @param allowedAddresses the allowed addresses
254      * @param lportTag the lport tag
255      * @param addOrRemove whether to add or remove the flow
256      */
257     private void egressAclDhcpv6AllowClientTraffic(List<FlowEntity> flowEntries, AclInterface port,
258             List<AllowedAddressPairs> allowedAddresses, int lportTag, int addOrRemove) {
259         // if there is a duplicate mac with different aap, do not delete the Dhcp Allow rule.
260         if (hasDuplicateMac(port.getAllowedAddressPairs(), allowedAddresses)) {
261             return;
262         }
263         BigInteger dpId = port.getDpId();
264         List<InstructionInfo> instructions = getDispatcherTableResubmitInstructions();
265         for (AllowedAddressPairs aap : allowedAddresses) {
266             if (AclServiceUtils.isIPv4Address(aap)) {
267                 continue;
268             }
269             List<MatchInfoBase> matches = new ArrayList<>();
270             matches.addAll(AclServiceUtils.buildDhcpV6Matches(AclConstants.DHCP_CLIENT_PORT_IPV6,
271                 AclConstants.DHCP_SERVER_PORT_IPV6, lportTag, serviceMode));
272             matches.add(new MatchEthernetSource(aap.getMacAddress()));
273
274             String flowName = "Egress_DHCP_Client_v6" + "_" + dpId + "_" + lportTag + "_"
275                     + aap.getMacAddress().getValue() + "_Permit_";
276             addFlowEntryToList(flowEntries, dpId, getAclAntiSpoofingTable(), flowName,
277                     AclConstants.PROTO_DHCP_CLIENT_TRAFFIC_MATCH_PRIORITY, 0, 0, AclConstants.COOKIE_ACL_BASE,
278                     matches, instructions, addOrRemove);
279         }
280     }
281
282     /**
283      * Adds the rule to allow arp packets.
284      *
285      * @param flowEntries the flow entries
286      * @param dpId the dpId
287      * @param allowedAddresses the allowed addresses
288      * @param lportTag the lport tag
289      * @param addOrRemove whether to add or remove the flow
290      */
291     protected void programArpRule(List<FlowEntity> flowEntries, BigInteger dpId,
292             List<AllowedAddressPairs> allowedAddresses, int lportTag, int addOrRemove) {
293         for (AllowedAddressPairs allowedAddress : allowedAddresses) {
294             if (!AclServiceUtils.isIPv4Address(allowedAddress)) {
295                 continue; // For IPv6 allowed addresses
296             }
297
298             IpPrefixOrAddress allowedAddressIp = allowedAddress.getIpAddress();
299             MacAddress allowedAddressMac = allowedAddress.getMacAddress();
300             List<MatchInfoBase> arpIpMatches = AclServiceUtils.buildArpIpMatches(allowedAddressIp);
301             List<MatchInfoBase> matches = new ArrayList<>();
302             matches.add(MatchEthernetType.ARP);
303             matches.add(new MatchArpSha(allowedAddressMac));
304             matches.add(new MatchEthernetSource(allowedAddressMac));
305             matches.addAll(arpIpMatches);
306             matches.add(AclServiceUtils.buildLPortTagMatch(lportTag, serviceMode));
307
308             List<InstructionInfo> instructions = getDispatcherTableResubmitInstructions();
309             LOG.debug("{} ARP Rule on DPID {}, lportTag {}",
310                     addOrRemove == NwConstants.DEL_FLOW ? "Deleting" : "Adding", dpId, lportTag);
311             String flowName = "Egress_ARP_" + dpId + "_" + lportTag + "_" + allowedAddress.getMacAddress().getValue()
312                     + allowedAddressIp.stringValue();
313             addFlowEntryToList(flowEntries, dpId, getAclAntiSpoofingTable(), flowName,
314                     AclConstants.PROTO_ARP_TRAFFIC_MATCH_PRIORITY, 0, 0, AclConstants.COOKIE_ACL_BASE, matches,
315                     instructions, addOrRemove);
316         }
317     }
318
319     @Override
320     protected void programIcmpv6RARule(List<FlowEntity> flowEntries, AclInterface port, List<SubnetInfo> subnets,
321             int addOrRemove) {
322         // No action required on egress.
323     }
324
325     /**
326      * Programs broadcast rules.
327      *
328      * @param flowEntries the flow entries
329      * @param port the Acl Interface port
330      * @param addOrRemove whether to delete or add flow
331      */
332     @Override
333     protected void programBroadcastRules(List<FlowEntity> flowEntries, AclInterface port, int addOrRemove) {
334         programL2BroadcastAllowRule(flowEntries, port,
335                 AclServiceUtils.excludeMulticastAAPs(port.getAllowedAddressPairs()), addOrRemove);
336     }
337
338     /**
339      * Programs Non-IP broadcast rules.
340      *
341      * @param flowEntries the flow entries
342      * @param port the Acl Interface port
343      * @param filteredAAPs the filtered AAPs list
344      * @param addOrRemove whether to delete or add flow
345      */
346     private void programL2BroadcastAllowRule(List<FlowEntity> flowEntries, AclInterface port,
347             List<AllowedAddressPairs> filteredAAPs, int addOrRemove) {
348         // if there is a duplicate mac with different aap, do not delete the Broadcast rule.
349         if (hasDuplicateMac(port.getAllowedAddressPairs(), filteredAAPs)) {
350             return;
351         }
352         BigInteger dpId = port.getDpId();
353         int lportTag = port.getLPortTag();
354         Set<MacAddress> macs = filteredAAPs.stream().map(AllowedAddressPairs::getMacAddress)
355                 .collect(Collectors.toSet());
356         for (MacAddress mac : macs) {
357             List<MatchInfoBase> matches = new ArrayList<>();
358             matches.add(new MatchEthernetSource(mac));
359             matches.add(AclServiceUtils.buildLPortTagMatch(lportTag, serviceMode));
360
361             List<InstructionInfo> instructions = getDispatcherTableResubmitInstructions();
362
363             String flowName = "Egress_L2Broadcast_" + dpId + "_" + lportTag + "_" + mac.getValue();
364             addFlowEntryToList(flowEntries, dpId, getAclAntiSpoofingTable(), flowName,
365                     AclConstants.PROTO_L2BROADCAST_TRAFFIC_MATCH_PRIORITY, 0, 0, AclConstants.COOKIE_ACL_BASE,
366                     matches, instructions, addOrRemove);
367         }
368     }
369
370     private boolean hasDuplicateMac(List<AllowedAddressPairs> allowedAddresses,
371             List<AllowedAddressPairs> filteredAAPs) {
372         // Do not proceed further if VM delete or Port down event.
373         if (allowedAddresses.size() == filteredAAPs.size()) {
374             return false;
375         }
376         //exclude filteredAAP entries from port's AAP's before comparison
377         List<AllowedAddressPairs> filteredAllowedAddressed = allowedAddresses.stream().filter(
378             aap -> !filteredAAPs.contains(aap)).collect(Collectors.toList());
379         Set<MacAddress> macs = filteredAAPs.stream().map(AllowedAddressPairs::getMacAddress)
380                 .collect(Collectors.toSet());
381         List<AllowedAddressPairs> aapWithDuplicateMac = filteredAllowedAddressed.stream()
382             .filter(entry -> macs.contains(entry.getMacAddress())).collect(Collectors.toList());
383         return !aapWithDuplicateMac.isEmpty();
384     }
385
386     @Override
387     protected boolean isValidDirection(Class<? extends DirectionBase> direction) {
388         return direction.equals(DirectionEgress.class);
389     }
390
391     private short getAclAntiSpoofingTable() {
392         return NwConstants.INGRESS_ACL_ANTI_SPOOFING_TABLE;
393     }
394
395     private short getAclConntrackClassifierTable() {
396         return NwConstants.INGRESS_ACL_CONNTRACK_CLASSIFIER_TABLE;
397     }
398
399     @Override
400     protected short getAclConntrackSenderTable() {
401         return NwConstants.INGRESS_ACL_CONNTRACK_SENDER_TABLE;
402     }
403
404     @Override
405     protected short getAclForExistingTrafficTable() {
406         return NwConstants.INGRESS_ACL_FOR_EXISTING_TRAFFIC_TABLE;
407     }
408
409     @Override
410     protected short getAclFilterCumDispatcherTable() {
411         return NwConstants.INGRESS_ACL_FILTER_CUM_DISPATCHER_TABLE;
412     }
413
414     @Override
415     protected short getAclRuleBasedFilterTable() {
416         return NwConstants.INGRESS_ACL_RULE_BASED_FILTER_TABLE;
417     }
418
419     @Override
420     protected short getAclRemoteAclTable() {
421         return NwConstants.INGRESS_REMOTE_ACL_TABLE;
422     }
423
424     @Override
425     protected short getAclCommitterTable() {
426         return NwConstants.INGRESS_ACL_COMMITTER_TABLE;
427     }
428 }