blob: 5acc4343b8c763e8e9cc1cd783509c1243f4f4b1 [file] [log] [blame]
Carmelo Cascone700648c2018-04-11 12:02:16 -07001/*
2 * Copyright 2017-present Open Networking Foundation
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
17package org.onosproject.p4tutorial.mytunnel;
18
19import com.google.common.collect.Lists;
20import org.apache.felix.scr.annotations.Activate;
21import org.apache.felix.scr.annotations.Component;
22import org.apache.felix.scr.annotations.Deactivate;
23import org.apache.felix.scr.annotations.Reference;
24import org.apache.felix.scr.annotations.ReferenceCardinality;
25import org.onlab.packet.IpAddress;
26import org.onosproject.core.ApplicationId;
27import org.onosproject.core.CoreService;
28import org.onosproject.net.DeviceId;
29import org.onosproject.net.Host;
30import org.onosproject.net.Link;
31import org.onosproject.net.Path;
32import org.onosproject.net.PortNumber;
33import org.onosproject.net.flow.DefaultFlowRule;
34import org.onosproject.net.flow.DefaultTrafficSelector;
35import org.onosproject.net.flow.DefaultTrafficTreatment;
36import org.onosproject.net.flow.FlowRule;
37import org.onosproject.net.flow.FlowRuleService;
38import org.onosproject.net.flow.criteria.PiCriterion;
39import org.onosproject.net.host.HostEvent;
40import org.onosproject.net.host.HostListener;
41import org.onosproject.net.host.HostService;
42import org.onosproject.net.pi.model.PiActionId;
43import org.onosproject.net.pi.model.PiActionParamId;
44import org.onosproject.net.pi.model.PiMatchFieldId;
45import org.onosproject.net.pi.model.PiTableId;
46import org.onosproject.net.pi.runtime.PiAction;
47import org.onosproject.net.pi.runtime.PiActionParam;
48import org.onosproject.net.topology.Topology;
49import org.onosproject.net.topology.TopologyService;
50import org.slf4j.Logger;
51
52import java.util.Collections;
53import java.util.List;
54import java.util.Random;
55import java.util.Set;
56import java.util.concurrent.atomic.AtomicInteger;
57
Carmelo Cascone700648c2018-04-11 12:02:16 -070058import static org.slf4j.LoggerFactory.getLogger;
59
60/**
61 * MyTunnel application which provides forwarding between each pair of hosts via
62 * MyTunnel protocol as defined in mytunnel.p4.
63 * <p>
64 * The app works by listening for host events. Each time a new host is
65 * discovered, it provisions a tunnel between that host and all the others.
66 */
67@Component(immediate = true)
68public class MyTunnelApp {
69
70 private static final String APP_NAME = "org.onosproject.p4tutorial.mytunnel";
71
72 // Default priority used for flow rules installed by this app.
73 private static final int FLOW_RULE_PRIORITY = 100;
74
75 private final HostListener hostListener = new InternalHostListener();
76 private ApplicationId appId;
77 private AtomicInteger nextTunnelId = new AtomicInteger();
78
79 private static final Logger log = getLogger(MyTunnelApp.class);
80
81 //--------------------------------------------------------------------------
Carmelo Cascone12c97282018-04-19 16:29:25 +090082 // ONOS core services needed by this application.
Carmelo Cascone700648c2018-04-11 12:02:16 -070083 //--------------------------------------------------------------------------
84
85 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
86 private FlowRuleService flowRuleService;
87
88 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
89 private CoreService coreService;
90
91 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
92 private TopologyService topologyService;
93
94 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
95 private HostService hostService;
96
97 //--------------------------------------------------------------------------
98 //--------------------------------------------------------------------------
99
100 @Activate
101 public void activate() {
102 // Register app and event listeners.
103 log.info("Starting...");
104 appId = coreService.registerApplication(APP_NAME);
105 hostService.addListener(hostListener);
106 log.info("STARTED", appId.id());
107 }
108
109 @Deactivate
110 public void deactivate() {
111 // Remove listeners and clean-up flow rules.
112 log.info("Stopping...");
113 hostService.removeListener(hostListener);
114 flowRuleService.removeFlowRulesById(appId);
115 log.info("STOPPED");
116 }
117
118 /**
119 * Provisions a tunnel between the given source and destination host with
120 * the given tunnel ID. The tunnel is established using a randomly picked
121 * shortest path based on the given topology snapshot.
122 *
123 * @param tunId tunnel ID
124 * @param srcHost tunnel source host
125 * @param dstHost tunnel destination host
126 * @param topo topology snapshot
127 */
128 private void provisionTunnel(int tunId, Host srcHost, Host dstHost, Topology topo) {
129
130 // Get all shortest paths between switches connected to source and
131 // destination hosts.
132 DeviceId srcSwitch = srcHost.location().deviceId();
133 DeviceId dstSwitch = dstHost.location().deviceId();
134
135 List<Link> pathLinks;
136 if (srcSwitch.equals(dstSwitch)) {
137 // Source and dest hosts are connected to the same switch.
138 pathLinks = Collections.emptyList();
139 } else {
140 // Compute shortest path.
141 Set<Path> allPaths = topologyService.getPaths(topo, srcSwitch, dstSwitch);
142 if (allPaths.size() == 0) {
143 log.warn("No paths between {} and {}", srcHost.id(), dstHost.id());
144 return;
145 }
146 // If many shortest paths are available, pick a random one.
147 pathLinks = pickRandomPath(allPaths).links();
148 }
149
150 // Tunnel ingress rules.
151 for (IpAddress dstIpAddr : dstHost.ipAddresses()) {
152 // In ONOS discovered hosts can have multiple IP addresses.
153 // Insert tunnel ingress rule for each IP address.
154 // Next switches will forward based only on tunnel ID.
Carmelo Cascone0bd3b182018-04-18 18:59:39 +0900155 insertTunnelIngressRule(srcSwitch, dstIpAddr, tunId);
Carmelo Cascone700648c2018-04-11 12:02:16 -0700156 }
157
Carmelo Cascone0bd3b182018-04-18 18:59:39 +0900158 // Insert tunnel transit rules on all switches in the path, excluded the
Carmelo Cascone700648c2018-04-11 12:02:16 -0700159 // last one.
160 for (Link link : pathLinks) {
161 DeviceId sw = link.src().deviceId();
162 PortNumber port = link.src().port();
Carmelo Cascone0bd3b182018-04-18 18:59:39 +0900163 insertTunnelForwardRule(sw, port, tunId, false);
Carmelo Cascone700648c2018-04-11 12:02:16 -0700164 }
165
166 // Tunnel egress rule.
167 PortNumber egressSwitchPort = dstHost.location().port();
Carmelo Cascone0bd3b182018-04-18 18:59:39 +0900168 insertTunnelForwardRule(dstSwitch, egressSwitchPort, tunId, true);
Carmelo Cascone700648c2018-04-11 12:02:16 -0700169
170 log.info("** Completed provisioning of tunnel {} (srcHost={} dstHost={})",
171 tunId, srcHost.id(), dstHost.id());
172 }
173
174 /**
175 * Generates and insert a flow rule to perform the tunnel INGRESS function
176 * for the given switch, destination IP address and tunnel ID.
177 *
178 * @param switchId switch ID
179 * @param dstIpAddr IP address to forward inside the tunnel
180 * @param tunId tunnel ID
181 */
Carmelo Cascone0bd3b182018-04-18 18:59:39 +0900182 private void insertTunnelIngressRule(DeviceId switchId,
183 IpAddress dstIpAddr,
184 int tunId) {
Carmelo Cascone700648c2018-04-11 12:02:16 -0700185
Carmelo Cascone700648c2018-04-11 12:02:16 -0700186
Carmelo Cascone0bd3b182018-04-18 18:59:39 +0900187 PiTableId tunnelIngressTableId = PiTableId.of("c_ingress.t_tunnel_ingress");
188
189 // Longest prefix match on IPv4 dest address.
190 PiMatchFieldId ipDestMatchFieldId = PiMatchFieldId.of("hdr.ipv4.dst_addr");
Carmelo Cascone700648c2018-04-11 12:02:16 -0700191 PiCriterion match = PiCriterion.builder()
Carmelo Cascone0bd3b182018-04-18 18:59:39 +0900192 .matchLpm(ipDestMatchFieldId, dstIpAddr.toOctets(), 32)
Carmelo Cascone700648c2018-04-11 12:02:16 -0700193 .build();
194
Carmelo Cascone0bd3b182018-04-18 18:59:39 +0900195 PiActionParam tunIdParam = new PiActionParam(PiActionParamId.of("tun_id"), tunId);
196
197 PiActionId ingressActionId = PiActionId.of("c_ingress.my_tunnel_ingress");
Carmelo Cascone700648c2018-04-11 12:02:16 -0700198 PiAction action = PiAction.builder()
Carmelo Cascone0bd3b182018-04-18 18:59:39 +0900199 .withId(ingressActionId)
200 .withParameter(tunIdParam)
Carmelo Cascone700648c2018-04-11 12:02:16 -0700201 .build();
202
Carmelo Cascone0bd3b182018-04-18 18:59:39 +0900203 log.info("Inserting INGRESS rule on switch {}: table={}, match={}, action={}",
204 switchId, tunnelIngressTableId, match, action);
205
206 insertPiFlowRule(switchId, tunnelIngressTableId, match, action);
Carmelo Cascone700648c2018-04-11 12:02:16 -0700207 }
208
209 /**
210 * Generates and insert a flow rule to perform the tunnel FORWARD/EGRESS
211 * function for the given switch, output port address and tunnel ID.
212 *
213 * @param switchId switch ID
214 * @param outPort output port where to forward tunneled packets
Carmelo Cascone0bd3b182018-04-18 18:59:39 +0900215 * @param tunId tunnel ID
Carmelo Cascone700648c2018-04-11 12:02:16 -0700216 * @param isEgress if true, perform tunnel egress action, otherwise forward
217 * packet as is to port
218 */
Carmelo Cascone0bd3b182018-04-18 18:59:39 +0900219 private void insertTunnelForwardRule(DeviceId switchId,
220 PortNumber outPort,
221 int tunId,
222 boolean isEgress) {
Carmelo Cascone700648c2018-04-11 12:02:16 -0700223
Carmelo Cascone0bd3b182018-04-18 18:59:39 +0900224 PiTableId tunnelForwardTableId = PiTableId.of("c_ingress.t_tunnel_fwd");
Carmelo Cascone700648c2018-04-11 12:02:16 -0700225
Carmelo Cascone0bd3b182018-04-18 18:59:39 +0900226 // Exact match on tun_id
227 PiMatchFieldId tunIdMatchFieldId = PiMatchFieldId.of("hdr.my_tunnel.tun_id");
Carmelo Cascone700648c2018-04-11 12:02:16 -0700228 PiCriterion match = PiCriterion.builder()
Carmelo Cascone0bd3b182018-04-18 18:59:39 +0900229 .matchExact(tunIdMatchFieldId, tunId)
Carmelo Cascone700648c2018-04-11 12:02:16 -0700230 .build();
231
Carmelo Cascone0bd3b182018-04-18 18:59:39 +0900232 // Action depend on isEgress parameter.
233 // if true, perform tunnel egress action on the given outPort, otherwise
234 // simply forward packet as is (set_out_port action).
235 PiActionParamId portParamId = PiActionParamId.of("port");
236 PiActionParam portParam = new PiActionParam(portParamId, (short) outPort.toLong());
Carmelo Cascone700648c2018-04-11 12:02:16 -0700237
Carmelo Cascone0bd3b182018-04-18 18:59:39 +0900238 final PiAction action;
239 if (isEgress) {
240 // Tunnel egress action.
241 // Remove MyTunnel header and forward to outPort.
242 PiActionId egressActionId = PiActionId.of("c_ingress.my_tunnel_egress");
243 action = PiAction.builder()
244 .withId(egressActionId)
245 .withParameter(portParam)
246 .build();
247 } else {
248 // Tunnel transit action.
249 // Forward the packet as is to outPort.
250 /*
251 * TODO EXERCISE: create action object for the transit case.
252 * Look at the t_tunnel_fwd table in the P4 program. Which of the 3
253 * actions can be used to simply set the output port? Get the full
254 * action name from the P4Info file, and use that when creating the
255 * PiActionId object. When creating the PiAction object, remember to
256 * add all action parameters as defined in the P4 program.
257 *
258 * Hint: the code will be similar to the case when isEgress = false.
259 */
260 action = null;
261 }
Carmelo Cascone700648c2018-04-11 12:02:16 -0700262
Carmelo Cascone0bd3b182018-04-18 18:59:39 +0900263 log.info("Inserting {} rule on switch {}: table={}, match={}, action={}",
264 isEgress ? "EGRESS" : "TRANSIT",
265 switchId, tunnelForwardTableId, match, action);
266
267 insertPiFlowRule(switchId, tunnelForwardTableId, match, action);
Carmelo Cascone700648c2018-04-11 12:02:16 -0700268 }
269
270 /**
271 * Inserts a flow rule in the system that using a PI criterion and action.
272 *
273 * @param switchId switch ID
274 * @param tableId table ID
275 * @param piCriterion PI criterion
276 * @param piAction PI action
277 */
278 private void insertPiFlowRule(DeviceId switchId, PiTableId tableId,
279 PiCriterion piCriterion, PiAction piAction) {
280 FlowRule rule = DefaultFlowRule.builder()
281 .forDevice(switchId)
282 .forTable(tableId)
283 .fromApp(appId)
284 .withPriority(FLOW_RULE_PRIORITY)
285 .makePermanent()
286 .withSelector(DefaultTrafficSelector.builder()
287 .matchPi(piCriterion).build())
288 .withTreatment(DefaultTrafficTreatment.builder()
289 .piTableAction(piAction).build())
290 .build();
291 flowRuleService.applyFlowRules(rule);
292 }
293
294 private int getNewTunnelId() {
295 return nextTunnelId.incrementAndGet();
296 }
297
298 private Path pickRandomPath(Set<Path> paths) {
299 int item = new Random().nextInt(paths.size());
300 List<Path> pathList = Lists.newArrayList(paths);
301 return pathList.get(item);
302 }
303
304 /**
305 * A listener of host events that provisions two tunnels for each pair of
306 * hosts when a new host is discovered.
307 */
308 private class InternalHostListener implements HostListener {
309
310 @Override
311 public void event(HostEvent event) {
312 if (event.type() != HostEvent.Type.HOST_ADDED) {
313 // Ignore other host events.
314 return;
315 }
316 synchronized (this) {
317 // Synchronizing here is an overkill, but safer for demo purposes.
318 Host host = event.subject();
319 Topology topo = topologyService.currentTopology();
320 for (Host otherHost : hostService.getHosts()) {
321 if (!host.equals(otherHost)) {
322 provisionTunnel(getNewTunnelId(), host, otherHost, topo);
323 provisionTunnel(getNewTunnelId(), otherHost, host, topo);
324 }
325 }
326 }
327 }
328 }
329}