blob: eb912434213b5f27216adb1add6a512479c729af [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
58import static org.onlab.util.ImmutableByteSequence.copyFrom;
59import static org.slf4j.LoggerFactory.getLogger;
60
61/**
62 * MyTunnel application which provides forwarding between each pair of hosts via
63 * MyTunnel protocol as defined in mytunnel.p4.
64 * <p>
65 * The app works by listening for host events. Each time a new host is
66 * discovered, it provisions a tunnel between that host and all the others.
67 */
68@Component(immediate = true)
69public class MyTunnelApp {
70
71 private static final String APP_NAME = "org.onosproject.p4tutorial.mytunnel";
72
73 // Default priority used for flow rules installed by this app.
74 private static final int FLOW_RULE_PRIORITY = 100;
75
76 private final HostListener hostListener = new InternalHostListener();
77 private ApplicationId appId;
78 private AtomicInteger nextTunnelId = new AtomicInteger();
79
80 private static final Logger log = getLogger(MyTunnelApp.class);
81
82 //--------------------------------------------------------------------------
83 // P4 program entity names. Values derived from mytunnel.p4info
84 //--------------------------------------------------------------------------
85
86 // Match field IDs.
87 private static final PiMatchFieldId MF_ID_MY_TUNNEL_DST_ID =
88 PiMatchFieldId.of("hdr.my_tunnel.tun_id");
89 private static final PiMatchFieldId MF_ID_IP_DST =
90 PiMatchFieldId.of("hdr.ipv4.dst_addr");
91 // Table IDs.
92 private static final PiTableId TBL_ID_TUNNEL_FWD =
93 PiTableId.of("c_ingress.t_tunnel_fwd");
94 private static final PiTableId TBL_ID_TUNNEL_INGRESS =
95 PiTableId.of("c_ingress.t_tunnel_ingress");
96 // Action IDs.
97 private static final PiActionId ACT_ID_MY_TUNNEL_INGRESS =
98 PiActionId.of("c_ingress.my_tunnel_ingress");
99 private static final PiActionId ACT_ID_SET_OUT_PORT =
100 PiActionId.of("c_ingress.set_out_port");
101 private static final PiActionId ACT_ID_MY_TUNNEL_EGRESS =
102 PiActionId.of("c_ingress.my_tunnel_egress");
103
104 // Action params ID.
105 private static final PiActionParamId ACT_PARAM_ID_TUN_ID =
106 PiActionParamId.of("tun_id");
107 private static final PiActionParamId ACT_PARAM_ID_PORT =
108 PiActionParamId.of("port");
109
110 //--------------------------------------------------------------------------
111 // ONOS services needed by this application.
112 //--------------------------------------------------------------------------
113
114 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
115 private FlowRuleService flowRuleService;
116
117 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
118 private CoreService coreService;
119
120 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
121 private TopologyService topologyService;
122
123 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
124 private HostService hostService;
125
126 //--------------------------------------------------------------------------
127 //--------------------------------------------------------------------------
128
129 @Activate
130 public void activate() {
131 // Register app and event listeners.
132 log.info("Starting...");
133 appId = coreService.registerApplication(APP_NAME);
134 hostService.addListener(hostListener);
135 log.info("STARTED", appId.id());
136 }
137
138 @Deactivate
139 public void deactivate() {
140 // Remove listeners and clean-up flow rules.
141 log.info("Stopping...");
142 hostService.removeListener(hostListener);
143 flowRuleService.removeFlowRulesById(appId);
144 log.info("STOPPED");
145 }
146
147 /**
148 * Provisions a tunnel between the given source and destination host with
149 * the given tunnel ID. The tunnel is established using a randomly picked
150 * shortest path based on the given topology snapshot.
151 *
152 * @param tunId tunnel ID
153 * @param srcHost tunnel source host
154 * @param dstHost tunnel destination host
155 * @param topo topology snapshot
156 */
157 private void provisionTunnel(int tunId, Host srcHost, Host dstHost, Topology topo) {
158
159 // Get all shortest paths between switches connected to source and
160 // destination hosts.
161 DeviceId srcSwitch = srcHost.location().deviceId();
162 DeviceId dstSwitch = dstHost.location().deviceId();
163
164 List<Link> pathLinks;
165 if (srcSwitch.equals(dstSwitch)) {
166 // Source and dest hosts are connected to the same switch.
167 pathLinks = Collections.emptyList();
168 } else {
169 // Compute shortest path.
170 Set<Path> allPaths = topologyService.getPaths(topo, srcSwitch, dstSwitch);
171 if (allPaths.size() == 0) {
172 log.warn("No paths between {} and {}", srcHost.id(), dstHost.id());
173 return;
174 }
175 // If many shortest paths are available, pick a random one.
176 pathLinks = pickRandomPath(allPaths).links();
177 }
178
179 // Tunnel ingress rules.
180 for (IpAddress dstIpAddr : dstHost.ipAddresses()) {
181 // In ONOS discovered hosts can have multiple IP addresses.
182 // Insert tunnel ingress rule for each IP address.
183 // Next switches will forward based only on tunnel ID.
184 insertIngressRule(srcSwitch, dstIpAddr, tunId);
185 }
186
187 // Insert tunnel forward rules on all switches in the path, excluded the
188 // last one.
189 for (Link link : pathLinks) {
190 DeviceId sw = link.src().deviceId();
191 PortNumber port = link.src().port();
192 insertForwardRule(sw, port, tunId, false);
193 }
194
195 // Tunnel egress rule.
196 PortNumber egressSwitchPort = dstHost.location().port();
197 insertForwardRule(dstSwitch, egressSwitchPort, tunId, true);
198
199 log.info("** Completed provisioning of tunnel {} (srcHost={} dstHost={})",
200 tunId, srcHost.id(), dstHost.id());
201 }
202
203 /**
204 * Generates and insert a flow rule to perform the tunnel INGRESS function
205 * for the given switch, destination IP address and tunnel ID.
206 *
207 * @param switchId switch ID
208 * @param dstIpAddr IP address to forward inside the tunnel
209 * @param tunId tunnel ID
210 */
211 private void insertIngressRule(DeviceId switchId,
212 IpAddress dstIpAddr,
213 int tunId) {
214
215 log.info("Inserting INGRESS rule: switchId={}, dstIpAddr={}, tunId={}",
216 switchId, dstIpAddr, tunId);
217
218 PiCriterion match = PiCriterion.builder()
219 .matchLpm(MF_ID_IP_DST, dstIpAddr.toOctets(), 32)
220 .build();
221
222 PiAction action = PiAction.builder()
223 .withId(ACT_ID_MY_TUNNEL_INGRESS)
224 .withParameter(new PiActionParam(
225 ACT_PARAM_ID_TUN_ID, copyFrom(tunId)))
226 .build();
227
228 insertPiFlowRule(switchId, TBL_ID_TUNNEL_INGRESS, match, action);
229 }
230
231 /**
232 * Generates and insert a flow rule to perform the tunnel FORWARD/EGRESS
233 * function for the given switch, output port address and tunnel ID.
234 *
235 * @param switchId switch ID
236 * @param outPort output port where to forward tunneled packets
237 * @param tunId tunnel ID
238 * @param isEgress if true, perform tunnel egress action, otherwise forward
239 * packet as is to port
240 */
241 private void insertForwardRule(DeviceId switchId,
242 PortNumber outPort,
243 int tunId,
244 boolean isEgress) {
245
246 log.info("Inserting {} rule: switchId={}, outPort={}, tunId={}",
247 isEgress ? "EGRESS" : "FORWARD", switchId, outPort, tunId);
248
249 PiCriterion match = PiCriterion.builder()
250 .matchExact(MF_ID_MY_TUNNEL_DST_ID, tunId)
251 .build();
252
253 PiActionId actionId = isEgress ? ACT_ID_MY_TUNNEL_EGRESS : ACT_ID_SET_OUT_PORT;
254
255 PiAction action = PiAction.builder()
256 .withId(actionId)
257 .withParameter(new PiActionParam(
258 ACT_PARAM_ID_PORT, copyFrom((short) outPort.toLong())))
259 .build();
260
261 insertPiFlowRule(switchId, TBL_ID_TUNNEL_FWD, 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}