c74845ce7adf76c4b5d86e242024b6c0299af433
[hc2vpp.git] /
1 /*
2  * Copyright (c) 2016 Cisco and/or its affiliates.
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
17 package io.fd.honeycomb.translate.v3po.interfaces.acl.ingress;
18
19 import static com.google.common.base.Preconditions.checkArgument;
20
21 import com.google.common.base.Optional;
22 import com.google.common.base.Preconditions;
23 import io.fd.honeycomb.translate.vpp.util.JvppReplyConsumer;
24 import io.fd.honeycomb.translate.write.WriteContext;
25 import io.fd.honeycomb.translate.write.WriteFailedException;
26 import io.fd.vpp.jvpp.core.dto.ClassifyAddDelSession;
27 import io.fd.vpp.jvpp.core.dto.ClassifyAddDelSessionReply;
28 import io.fd.vpp.jvpp.core.dto.ClassifyAddDelTable;
29 import io.fd.vpp.jvpp.core.dto.ClassifyAddDelTableReply;
30 import io.fd.vpp.jvpp.core.dto.ClassifyTableByInterface;
31 import io.fd.vpp.jvpp.core.dto.ClassifyTableByInterfaceReply;
32 import io.fd.vpp.jvpp.core.dto.InputAclSetInterface;
33 import io.fd.vpp.jvpp.core.dto.InputAclSetInterfaceReply;
34 import io.fd.vpp.jvpp.core.future.FutureJVppCore;
35 import java.util.HashMap;
36 import java.util.List;
37 import java.util.ListIterator;
38 import java.util.Map;
39 import java.util.concurrent.CompletionStage;
40 import java.util.function.Predicate;
41 import java.util.stream.Collectors;
42 import java.util.stream.Stream;
43 import javax.annotation.Nonnegative;
44 import javax.annotation.Nonnull;
45 import javax.annotation.Nullable;
46 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.access.control.list.rev160708.AclBase;
47 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.access.control.list.rev160708.access.lists.AclKey;
48 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.access.control.list.rev160708.access.lists.acl.AccessListEntries;
49 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.access.control.list.rev160708.access.lists.acl.access.list.entries.Ace;
50 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.access.control.list.rev160708.access.lists.acl.access.list.entries.ace.actions.PacketHandling;
51 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.access.control.list.rev160708.access.lists.acl.access.list.entries.ace.matches.AceType;
52 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.access.control.list.rev160708.access.lists.acl.access.list.entries.ace.matches.ace.type.AceEth;
53 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.access.control.list.rev160708.access.lists.acl.access.list.entries.ace.matches.ace.type.AceIp;
54 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.access.control.list.rev160708.access.lists.acl.access.list.entries.ace.matches.ace.type.ace.ip.AceIpVersion;
55 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.access.control.list.rev160708.access.lists.acl.access.list.entries.ace.matches.ace.type.ace.ip.ace.ip.version.AceIpv4;
56 import org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.access.control.list.rev160708.access.lists.acl.access.list.entries.ace.matches.ace.type.ace.ip.ace.ip.version.AceIpv6;
57 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.vpp.acl.rev161214.InterfaceMode;
58 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.vpp.acl.rev161214.access.lists.acl.access.list.entries.ace.matches.ace.type.AceIpAndEth;
59 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.vpp.acl.rev161214.ietf.acl.base.attributes.AccessLists;
60 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.vpp.acl.rev161214.ietf.acl.base.attributes.access.lists.Acl;
61 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
62 import org.slf4j.Logger;
63 import org.slf4j.LoggerFactory;
64
65 public final class IetfAclWriter implements JvppReplyConsumer, AclTranslator {
66
67     private static final Logger LOG = LoggerFactory.getLogger(IetfAclWriter.class);
68     private static final int NOT_DEFINED = -1;
69     private final FutureJVppCore jvpp;
70
71     private Map<AclType, AceWriter<? extends AceType>> aceWriters = new HashMap<>();
72
73     public IetfAclWriter(@Nonnull final FutureJVppCore futureJVppCore) {
74         this.jvpp = Preconditions.checkNotNull(futureJVppCore, "futureJVppCore should not be null");
75         aceWriters.put(AclType.ETH, new AceEthWriter());
76         aceWriters.put(AclType.IP4, new AceIp4Writer());
77         aceWriters.put(AclType.IP6, new AceIp6Writer());
78         aceWriters.put(AclType.ETH_AND_IP, new AceIpAndEthWriter());
79     }
80
81     private static Stream<Ace> aclToAceStream(@Nonnull final Acl assignedAcl,
82                                               @Nonnull final WriteContext writeContext) {
83         final String aclName = assignedAcl.getName();
84         final Class<? extends AclBase> aclType = assignedAcl.getType();
85
86         // ietf-acl updates are handled first, so we use writeContext.readAfter
87         final Optional<org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.access.control.list.rev160708.access.lists.Acl>
88             aclOptional =
89             writeContext.readAfter(io.fd.honeycomb.translate.v3po.interfaces.acl.IetfAclWriter.ACL_ID.child(
90                 org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.access.control.list.rev160708.access.lists.Acl.class,
91                 new AclKey(aclName, aclType)));
92         checkArgument(aclOptional.isPresent(), "Acl lists not configured");
93         final org.opendaylight.yang.gen.v1.urn.ietf.params.xml.ns.yang.ietf.access.control.list.rev160708.access.lists.Acl
94             acl = aclOptional.get();
95
96         final AccessListEntries accessListEntries = acl.getAccessListEntries();
97         checkArgument(accessListEntries != null, "access list entries not configured");
98
99         return accessListEntries.getAce().stream();
100     }
101
102     void deleteAcl(@Nonnull final InstanceIdentifier<?> id, final int swIfIndex)
103         throws WriteFailedException {
104         final ClassifyTableByInterface request = new ClassifyTableByInterface();
105         request.swIfIndex = swIfIndex;
106
107         final CompletionStage<ClassifyTableByInterfaceReply> cs = jvpp.classifyTableByInterface(request);
108         final ClassifyTableByInterfaceReply reply = getReplyForDelete(cs.toCompletableFuture(), id);
109
110         // We unassign and remove all ACL-related classify tables for given interface (we assume we are the only
111         // classify table manager)
112
113         unassignClassifyTables(id, reply);
114
115         removeClassifyTable(id, reply.l2TableId);
116         removeClassifyTable(id, reply.ip4TableId);
117         removeClassifyTable(id, reply.ip6TableId);
118     }
119
120     private void unassignClassifyTables(@Nonnull final InstanceIdentifier<?> id,
121                                         final ClassifyTableByInterfaceReply currentState)
122         throws WriteFailedException {
123         final InputAclSetInterface request = new InputAclSetInterface();
124         request.isAdd = 0;
125         request.swIfIndex = currentState.swIfIndex;
126         request.l2TableIndex = currentState.l2TableId;
127         request.ip4TableIndex = currentState.ip4TableId;
128         request.ip6TableIndex = currentState.ip6TableId;
129         final CompletionStage<InputAclSetInterfaceReply> inputAclSetInterfaceReplyCompletionStage =
130             jvpp.inputAclSetInterface(request);
131         getReplyForDelete(inputAclSetInterfaceReplyCompletionStage.toCompletableFuture(), id);
132     }
133
134     private void removeClassifyTable(@Nonnull final InstanceIdentifier<?> id, final int tableIndex)
135         throws WriteFailedException {
136
137         if (tableIndex == -1) {
138             return; // classify table id is absent
139         }
140         final ClassifyAddDelTable request = new ClassifyAddDelTable();
141         request.tableIndex = tableIndex;
142         final CompletionStage<ClassifyAddDelTableReply> cs = jvpp.classifyAddDelTable(request);
143         getReplyForDelete(cs.toCompletableFuture(), id);
144     }
145
146     void write(@Nonnull final InstanceIdentifier<?> id, final int swIfIndex, @Nonnull final List<Acl> acls,
147                final AccessLists.DefaultAction defaultAction, @Nullable final InterfaceMode mode,
148                @Nonnull final WriteContext writeContext)
149         throws WriteFailedException {
150         write(id, swIfIndex, mode, acls, defaultAction, writeContext, 0);
151     }
152
153     private static boolean appliesToIp4Path(final Ace ace) {
154         final AceType aceType = ace.getMatches().getAceType();
155         final AclType aclType = AclType.fromAce(ace);
156         if (aclType == AclType.IP4) {
157             return true;
158         }
159         if (aclType == AclType.ETH) {
160             return true;  // L2 only rules are possible for IP4 traffic
161         }
162         if (aclType == AclType.ETH_AND_IP && ((AceIpAndEth) aceType)
163             .getAceIpVersion() instanceof org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.vpp.acl.rev161214.access.lists.acl.access.list.entries.ace.matches.ace.type.ace.ip.and.eth.ace.ip.version.AceIpv4) {
164             return true;
165         }
166         return false;
167     }
168
169     private static boolean appliesToIp6Path(final Ace ace) {
170         final AceType aceType = ace.getMatches().getAceType();
171         final AclType aclType = AclType.fromAce(ace);
172         if (aclType == AclType.IP6) {
173             return true;
174         }
175         if (aclType == AclType.ETH) {
176             return true;  // L2 only rules are possible for IP6 traffic
177         }
178         if (aclType == AclType.ETH_AND_IP && ((AceIpAndEth) aceType)
179             .getAceIpVersion() instanceof org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.vpp.acl.rev161214.access.lists.acl.access.list.entries.ace.matches.ace.type.ace.ip.and.eth.ace.ip.version.AceIpv6) {
180             return true;
181         }
182         return false;
183     }
184
185     void write(@Nonnull final InstanceIdentifier<?> id, final int swIfIndex, final InterfaceMode mode,
186                @Nonnull final List<Acl> acls, final AccessLists.DefaultAction defaultAction,
187                @Nonnull final WriteContext writeContext, @Nonnegative final int numberOfTags)
188         throws WriteFailedException {
189         checkArgument(numberOfTags >= 0 && numberOfTags <= 2, "Number of vlan tags %s is not in [0,2] range");
190
191         final InputAclSetInterface request = new InputAclSetInterface();
192         request.isAdd = 1;
193         request.swIfIndex = swIfIndex;
194         request.l2TableIndex = NOT_DEFINED;
195         request.ip4TableIndex = NOT_DEFINED;
196         request.ip6TableIndex = NOT_DEFINED;
197
198         if (InterfaceMode.L2.equals(mode)) {
199             final List<Ace> aces = getACEs(acls, writeContext, ace -> true);
200             request.l2TableIndex = writeAces(id, aces, defaultAction, mode, numberOfTags);
201         } else {
202             final List<Ace> ip4Aces = getACEs(acls, writeContext, (IetfAclWriter::appliesToIp4Path));
203             request.ip4TableIndex = writeAces(id, ip4Aces, defaultAction, mode, numberOfTags);
204             final List<Ace> ip6Aces = getACEs(acls, writeContext, (IetfAclWriter::appliesToIp6Path));
205             request.ip6TableIndex = writeAces(id, ip6Aces, defaultAction, mode, numberOfTags);
206         }
207
208         final CompletionStage<InputAclSetInterfaceReply> inputAclSetInterfaceReplyCompletionStage =
209             jvpp.inputAclSetInterface(request);
210         getReplyForWrite(inputAclSetInterfaceReplyCompletionStage.toCompletableFuture(), id);
211     }
212
213     private static List<Ace> getACEs(@Nonnull final List<Acl> acls, @Nonnull final WriteContext writeContext,
214                                      final Predicate<? super Ace> filter) {
215         return acls.stream().flatMap(acl -> aclToAceStream(acl, writeContext)).filter(filter)
216             .collect(Collectors.toList());
217     }
218
219     private int writeAces(final InstanceIdentifier<?> id, final List<Ace> aces,
220                           final AccessLists.DefaultAction defaultAction, final InterfaceMode mode,
221                           final int vlanTags) throws WriteFailedException {
222         if (aces.isEmpty()) {
223             return NOT_DEFINED;
224         }
225
226         int nextTableIndex = configureDefaultAction(id, defaultAction);
227         final ListIterator<Ace> iterator = aces.listIterator(aces.size());
228         while (iterator.hasPrevious()) {
229             final Ace ace = iterator.previous();
230             LOG.trace("Processing ACE: {}", ace);
231
232             final AceWriter aceWriter =
233                 aceWriters.get(AclType.fromAce(ace));
234             if (aceWriter == null) {
235                 LOG.warn("AceProcessor for {} not registered. Skipping ACE.", ace.getClass());
236             } else {
237                 final AceType aceType = ace.getMatches().getAceType();
238                 final PacketHandling action = ace.getActions().getPacketHandling();
239                 final ClassifyAddDelTable ctRequest = aceWriter.createTable(aceType, mode, nextTableIndex, vlanTags);
240                 nextTableIndex = createClassifyTable(id, ctRequest);
241                 final List<ClassifyAddDelSession> sessionRequests =
242                     aceWriter.createSession(action, aceType, mode, nextTableIndex, vlanTags);
243                 for (ClassifyAddDelSession csRequest : sessionRequests) {
244                     createClassifySession(id, csRequest);
245                 }
246             }
247         }
248         return nextTableIndex;
249     }
250
251     private int configureDefaultAction(@Nonnull final InstanceIdentifier<?> id,
252                                        final AccessLists.DefaultAction defaultAction)
253         throws WriteFailedException {
254         ClassifyAddDelTable ctRequest = createTable(-1);
255         if (AccessLists.DefaultAction.Permit.equals(defaultAction)) {
256             ctRequest.missNextIndex = -1;
257         } else {
258             ctRequest.missNextIndex = 0;
259         }
260         ctRequest.mask = new byte[16];
261         ctRequest.skipNVectors = 0;
262         ctRequest.matchNVectors = 1;
263         return createClassifyTable(id, ctRequest);
264     }
265
266     private int createClassifyTable(@Nonnull final InstanceIdentifier<?> id,
267                                     @Nonnull final ClassifyAddDelTable request)
268         throws WriteFailedException {
269         final CompletionStage<ClassifyAddDelTableReply> cs = jvpp.classifyAddDelTable(request);
270
271         final ClassifyAddDelTableReply reply = getReplyForWrite(cs.toCompletableFuture(), id);
272         return reply.newTableIndex;
273     }
274
275     private void createClassifySession(@Nonnull final InstanceIdentifier<?> id,
276                                        @Nonnull final ClassifyAddDelSession request)
277         throws WriteFailedException {
278         final CompletionStage<ClassifyAddDelSessionReply> cs = jvpp.classifyAddDelSession(request);
279
280         getReplyForWrite(cs.toCompletableFuture(), id);
281     }
282
283     private enum AclType {
284         ETH, IP4, IP6, ETH_AND_IP;
285
286         @Nonnull
287         private static AclType fromAce(final Ace ace) {
288             AclType result = null;
289             final AceType aceType;
290             try {
291                 aceType = ace.getMatches().getAceType();
292                 if (aceType instanceof AceEth) {
293                     result = ETH;
294                 } else if (aceType instanceof AceIp) {
295                     final AceIpVersion aceIpVersion = ((AceIp) aceType).getAceIpVersion();
296                     if (aceIpVersion == null) {
297                         throw new IllegalArgumentException("Incomplete ACE (ip-version was not provided): " + ace);
298                     }
299                     if (aceIpVersion instanceof AceIpv4) {
300                         result = IP4;
301                     } else if (aceIpVersion instanceof AceIpv6) {
302                         result = IP6;
303                     }
304                 } else if (aceType instanceof AceIpAndEth) {
305                     result = ETH_AND_IP;
306                 }
307             } catch (NullPointerException e) {
308                 throw new IllegalArgumentException("Incomplete ACE: " + ace, e);
309             }
310             if (result == null) {
311                 throw new IllegalArgumentException(String.format("Not supported ace type %s", aceType));
312             }
313             return result;
314         }
315     }
316 }