blob: ab28e526066b27a858e4fa67e051c728743f1a56 [file] [log] [blame]
Carmelo Cascone0bd3b182018-04-18 18:59:39 +09001/*
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
58import 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 //--------------------------------------------------------------------------
82 // ONOS services needed by this application.
83 //--------------------------------------------------------------------------
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.
155 insertTunnelIngressRule(srcSwitch, dstIpAddr, tunId);
156 }
157
158 // Insert tunnel transit rules on all switches in the path, excluded the
159 // last one.
160 for (Link link : pathLinks) {
161 DeviceId sw = link.src().deviceId();
162 PortNumber port = link.src().port();
163 insertTunnelForwardRule(sw, port, tunId, false);
164 }
165
166 // Tunnel egress rule.
167 PortNumber egressSwitchPort = dstHost.location().port();
168 insertTunnelForwardRule(dstSwitch, egressSwitchPort, tunId, true);
169
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 */
182 private void insertTunnelIngressRule(DeviceId switchId,
183 IpAddress dstIpAddr,
184 int tunId) {
185
186
187 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");
191 PiCriterion match = PiCriterion.builder()
192 .matchLpm(ipDestMatchFieldId, dstIpAddr.toOctets(), 32)
193 .build();
194
195 PiActionParam tunIdParam = new PiActionParam(PiActionParamId.of("tun_id"), tunId);
196
197 PiActionId ingressActionId = PiActionId.of("c_ingress.my_tunnel_ingress");
198 PiAction action = PiAction.builder()
199 .withId(ingressActionId)
200 .withParameter(tunIdParam)
201 .build();
202
203 log.info("Inserting INGRESS rule on switch {}: table={}, match={}, action={}",
204 switchId, tunnelIngressTableId, match, action);
205
206 insertPiFlowRule(switchId, tunnelIngressTableId, match, action);
207 }
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
215 * @param tunId tunnel ID
216 * @param isEgress if true, perform tunnel egress action, otherwise forward
217 * packet as is to port
218 */
219 private void insertTunnelForwardRule(DeviceId switchId,
220 PortNumber outPort,
221 int tunId,
222 boolean isEgress) {
223
224 PiTableId tunnelForwardTableId = PiTableId.of("c_ingress.t_tunnel_fwd");
225
226 // Exact match on tun_id
227 PiMatchFieldId tunIdMatchFieldId = PiMatchFieldId.of("hdr.my_tunnel.tun_id");
228 PiCriterion match = PiCriterion.builder()
229 .matchExact(tunIdMatchFieldId, tunId)
230 .build();
231
232 // 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());
237
238 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 PiActionId egressActionId = PiActionId.of("c_ingress.set_out_port");
251 action = PiAction.builder()
252 .withId(egressActionId)
253 .withParameter(portParam)
254 .build();
255 }
256
257 log.info("Inserting {} rule on switch {}: table={}, match={}, action={}",
258 isEgress ? "EGRESS" : "TRANSIT",
259 switchId, tunnelForwardTableId, match, action);
260
261 insertPiFlowRule(switchId, tunnelForwardTableId, match, action);
262 }
263
264 /**
265 * Inserts a flow rule in the system that using a PI criterion and action.
266 *
267 * @param switchId switch ID
268 * @param tableId table ID
269 * @param piCriterion PI criterion
270 * @param piAction PI action
271 */
272 private void insertPiFlowRule(DeviceId switchId, PiTableId tableId,
273 PiCriterion piCriterion, PiAction piAction) {
274 FlowRule rule = DefaultFlowRule.builder()
275 .forDevice(switchId)
276 .forTable(tableId)
277 .fromApp(appId)
278 .withPriority(FLOW_RULE_PRIORITY)
279 .makePermanent()
280 .withSelector(DefaultTrafficSelector.builder()
281 .matchPi(piCriterion).build())
282 .withTreatment(DefaultTrafficTreatment.builder()
283 .piTableAction(piAction).build())
284 .build();
285 flowRuleService.applyFlowRules(rule);
286 }
287
288 private int getNewTunnelId() {
289 return nextTunnelId.incrementAndGet();
290 }
291
292 private Path pickRandomPath(Set<Path> paths) {
293 int item = new Random().nextInt(paths.size());
294 List<Path> pathList = Lists.newArrayList(paths);
295 return pathList.get(item);
296 }
297
298 /**
299 * A listener of host events that provisions two tunnels for each pair of
300 * hosts when a new host is discovered.
301 */
302 private class InternalHostListener implements HostListener {
303
304 @Override
305 public void event(HostEvent event) {
306 if (event.type() != HostEvent.Type.HOST_ADDED) {
307 // Ignore other host events.
308 return;
309 }
310 synchronized (this) {
311 // Synchronizing here is an overkill, but safer for demo purposes.
312 Host host = event.subject();
313 Topology topo = topologyService.currentTopology();
314 for (Host otherHost : hostService.getHosts()) {
315 if (!host.equals(otherHost)) {
316 provisionTunnel(getNewTunnelId(), host, otherHost, topo);
317 provisionTunnel(getNewTunnelId(), otherHost, host, topo);
318 }
319 }
320 }
321 }
322 }
323}