blob: f8428a7c9117254bbe8098ca1057cbbd1117d1bf [file] [log] [blame]
Charles Chan1eaf4802016-04-18 13:44:03 -07001/*
Brian O'Connor0947d7e2017-08-03 21:12:30 -07002 * Copyright 2016-present Open Networking Foundation
Charles Chan1eaf4802016-04-18 13:44:03 -07003 *
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.segmentrouting;
18
Charles Chan1eaf4802016-04-18 13:44:03 -070019import org.onlab.packet.IpAddress;
20import org.onlab.packet.MacAddress;
21import org.onlab.packet.VlanId;
Charles Chan10b0fb72017-02-02 16:20:42 -080022import org.onosproject.net.ConnectPoint;
Charles Chan1eaf4802016-04-18 13:44:03 -070023import org.onosproject.net.DeviceId;
Charles Chan370a65b2016-05-10 17:29:47 -070024import org.onosproject.net.Host;
Charles Chanc22cef32016-04-29 14:38:22 -070025import org.onosproject.net.HostLocation;
Charles Chan1eaf4802016-04-18 13:44:03 -070026import org.onosproject.net.PortNumber;
27import org.onosproject.net.flow.DefaultTrafficSelector;
28import org.onosproject.net.flow.DefaultTrafficTreatment;
29import org.onosproject.net.flow.TrafficSelector;
30import org.onosproject.net.flow.TrafficTreatment;
31import org.onosproject.net.flowobjective.DefaultForwardingObjective;
32import org.onosproject.net.flowobjective.DefaultObjectiveContext;
33import org.onosproject.net.flowobjective.FlowObjectiveService;
34import org.onosproject.net.flowobjective.ForwardingObjective;
35import org.onosproject.net.flowobjective.ObjectiveContext;
36import org.onosproject.net.host.HostEvent;
37import org.onosproject.net.host.HostService;
38import org.slf4j.Logger;
39import org.slf4j.LoggerFactory;
40
Saurav Dasf9332192017-02-18 14:05:44 -080041import com.google.common.collect.Sets;
Charles Chan3ed34d82017-06-22 18:03:14 -070042
43import java.util.Optional;
Charles Chan1eaf4802016-04-18 13:44:03 -070044import java.util.Set;
Charles Chand9265a32017-06-16 15:19:24 -070045import java.util.stream.Collectors;
46
47import static com.google.common.base.Preconditions.checkArgument;
Charles Chan1eaf4802016-04-18 13:44:03 -070048
49/**
50 * Handles host-related events.
51 */
52public class HostHandler {
53 private static final Logger log = LoggerFactory.getLogger(HostHandler.class);
Charles Chan114aec72017-06-19 14:00:53 -070054 protected final SegmentRoutingManager srManager;
Charles Chan1eaf4802016-04-18 13:44:03 -070055 private HostService hostService;
56 private FlowObjectiveService flowObjectiveService;
57
58 /**
59 * Constructs the HostHandler.
60 *
61 * @param srManager Segment Routing manager
62 */
Charles Chand9265a32017-06-16 15:19:24 -070063 HostHandler(SegmentRoutingManager srManager) {
Charles Chan1eaf4802016-04-18 13:44:03 -070064 this.srManager = srManager;
Charles Chan1eaf4802016-04-18 13:44:03 -070065 hostService = srManager.hostService;
66 flowObjectiveService = srManager.flowObjectiveService;
67 }
68
Charles Chandebfea32016-10-24 14:52:01 -070069 protected void init(DeviceId devId) {
Charles Chand9265a32017-06-16 15:19:24 -070070 hostService.getHosts().forEach(host ->
71 host.locations().stream()
72 .filter(location -> location.deviceId().equals(devId))
73 .forEach(location -> processHostAddedAtLocation(host, location))
74 );
Charles Chanc22cef32016-04-29 14:38:22 -070075 }
Charles Chan1eaf4802016-04-18 13:44:03 -070076
Charles Chand9265a32017-06-16 15:19:24 -070077 void processHostAddedEvent(HostEvent event) {
Charles Chan41f5ec02016-06-13 18:54:31 -070078 processHostAdded(event.subject());
Charles Chanc22cef32016-04-29 14:38:22 -070079 }
80
Charles Chand9265a32017-06-16 15:19:24 -070081 private void processHostAdded(Host host) {
82 host.locations().forEach(location -> processHostAddedAtLocation(host, location));
Charles Chan1eaf4802016-04-18 13:44:03 -070083 }
84
Charles Chand9265a32017-06-16 15:19:24 -070085 void processHostAddedAtLocation(Host host, HostLocation location) {
86 checkArgument(host.locations().contains(location), "{} is not a location of {}", location, host);
87
88 MacAddress mac = host.mac();
89 VlanId vlanId = host.vlan();
90 Set<HostLocation> locations = host.locations();
91 Set<IpAddress> ips = host.ipAddresses();
92 log.info("Host {}/{} is added at {}", mac, vlanId, locations);
93
94 processBridgingRule(location.deviceId(), location.port(), mac, vlanId, false);
95 ips.forEach(ip ->
96 processRoutingRule(location.deviceId(), location.port(), mac, vlanId, ip, false)
97 );
98 }
99
100 void processHostRemovedEvent(HostEvent event) {
Charles Chan41f5ec02016-06-13 18:54:31 -0700101 processHostRemoved(event.subject());
102 }
103
Charles Chand9265a32017-06-16 15:19:24 -0700104 private void processHostRemoved(Host host) {
Charles Chan3ed34d82017-06-22 18:03:14 -0700105 MacAddress hostMac = host.mac();
106 VlanId hostVlanId = host.vlan();
Charles Chand9265a32017-06-16 15:19:24 -0700107 Set<HostLocation> locations = host.locations();
Charles Chan41f5ec02016-06-13 18:54:31 -0700108 Set<IpAddress> ips = host.ipAddresses();
Charles Chan3ed34d82017-06-22 18:03:14 -0700109 log.info("Host {}/{} is removed from {}", hostMac, hostVlanId, locations);
Charles Chanc22cef32016-04-29 14:38:22 -0700110
Charles Chand9265a32017-06-16 15:19:24 -0700111 locations.forEach(location -> {
Charles Chan3ed34d82017-06-22 18:03:14 -0700112 processBridgingRule(location.deviceId(), location.port(), hostMac, hostVlanId, true);
Charles Chand9265a32017-06-16 15:19:24 -0700113 ips.forEach(ip ->
Charles Chan3ed34d82017-06-22 18:03:14 -0700114 processRoutingRule(location.deviceId(), location.port(), hostMac, hostVlanId, ip, true)
Charles Chand9265a32017-06-16 15:19:24 -0700115 );
Charles Chan3ed34d82017-06-22 18:03:14 -0700116
117 // Also remove redirection flows on the pair device if exists.
118 Optional<DeviceId> pairDeviceId = srManager.getPairDeviceId(location.deviceId());
119 Optional<PortNumber> pairLocalPort = srManager.getPairLocalPorts(location.deviceId());
120 if (pairDeviceId.isPresent() && pairLocalPort.isPresent()) {
121 // NOTE: Since the pairLocalPort is trunk port, use assigned vlan of original port
122 // when the host is untagged
123 VlanId vlanId = Optional.ofNullable(srManager.getInternalVlanId(location)).orElse(hostVlanId);
124
125 processBridgingRule(pairDeviceId.get(), pairLocalPort.get(), hostMac, vlanId, true);
126 ips.forEach(ip ->
127 processRoutingRule(pairDeviceId.get(), pairLocalPort.get(), hostMac, vlanId,
128 ip, true));
129 }
Charles Chand9265a32017-06-16 15:19:24 -0700130 });
Charles Chanc22cef32016-04-29 14:38:22 -0700131 }
132
Charles Chand9265a32017-06-16 15:19:24 -0700133 void processHostMovedEvent(HostEvent event) {
Charles Chan3ed34d82017-06-22 18:03:14 -0700134 MacAddress hostMac = event.subject().mac();
135 VlanId hostVlanId = event.subject().vlan();
Charles Chand9265a32017-06-16 15:19:24 -0700136 Set<HostLocation> prevLocations = event.prevSubject().locations();
Charles Chanc22cef32016-04-29 14:38:22 -0700137 Set<IpAddress> prevIps = event.prevSubject().ipAddresses();
Charles Chand9265a32017-06-16 15:19:24 -0700138 Set<HostLocation> newLocations = event.subject().locations();
Charles Chanc22cef32016-04-29 14:38:22 -0700139 Set<IpAddress> newIps = event.subject().ipAddresses();
Charles Chan3ed34d82017-06-22 18:03:14 -0700140 log.info("Host {}/{} is moved from {} to {}", hostMac, hostVlanId, prevLocations, newLocations);
Charles Chanc22cef32016-04-29 14:38:22 -0700141
Charles Chand9265a32017-06-16 15:19:24 -0700142 Set<DeviceId> newDeviceIds = newLocations.stream().map(HostLocation::deviceId)
143 .collect(Collectors.toSet());
Charles Chanc22cef32016-04-29 14:38:22 -0700144
Charles Chand9265a32017-06-16 15:19:24 -0700145 // For each old location
146 Sets.difference(prevLocations, newLocations).forEach(prevLocation -> {
Charles Chan3ed34d82017-06-22 18:03:14 -0700147 // Remove routing rules for old IPs
148 Sets.difference(prevIps, newIps).forEach(ip ->
149 processRoutingRule(prevLocation.deviceId(), prevLocation.port(), hostMac, hostVlanId,
150 ip, true)
151 );
152
153 // Redirect the flows to pair link if configured
154 // Note: Do not continue removing any rule
155 Optional<DeviceId> pairDeviceId = srManager.getPairDeviceId(prevLocation.deviceId());
156 Optional<PortNumber> pairLocalPort = srManager.getPairLocalPorts(prevLocation.deviceId());
157 if (pairDeviceId.isPresent() && pairLocalPort.isPresent() && newLocations.stream()
158 .anyMatch(location -> location.deviceId().equals(pairDeviceId.get()))) {
159 // NOTE: Since the pairLocalPort is trunk port, use assigned vlan of original port
160 // when the host is untagged
161 VlanId vlanId = Optional.ofNullable(srManager.getInternalVlanId(prevLocation)).orElse(hostVlanId);
162
163 processBridgingRule(prevLocation.deviceId(), pairLocalPort.get(), hostMac, vlanId, false);
164 newIps.forEach(ip ->
165 processRoutingRule(prevLocation.deviceId(), pairLocalPort.get(), hostMac, vlanId,
166 ip, false));
167 return;
168 }
Charles Chand9265a32017-06-16 15:19:24 -0700169
170 // Remove bridging rule and routing rules for unchanged IPs if the host moves from a switch to another.
171 // Otherwise, do not remove and let the adding part update the old flow
172 if (!newDeviceIds.contains(prevLocation.deviceId())) {
Charles Chan3ed34d82017-06-22 18:03:14 -0700173 processBridgingRule(prevLocation.deviceId(), prevLocation.port(), hostMac, hostVlanId, true);
Charles Chand9265a32017-06-16 15:19:24 -0700174 Sets.intersection(prevIps, newIps).forEach(ip ->
Charles Chan3ed34d82017-06-22 18:03:14 -0700175 processRoutingRule(prevLocation.deviceId(), prevLocation.port(), hostMac, hostVlanId,
Charles Chand9265a32017-06-16 15:19:24 -0700176 ip, true)
177 );
178 }
179
180 // Remove bridging rules if new interface vlan is different from old interface vlan
181 // Otherwise, do not remove and let the adding part update the old flow
182 if (newLocations.stream().noneMatch(newLocation -> {
183 VlanId oldAssignedVlan = srManager.getInternalVlanId(prevLocation);
184 VlanId newAssignedVlan = srManager.getInternalVlanId(newLocation);
185 // Host is tagged and the new location has the host vlan in vlan-tagged
Charles Chan3ed34d82017-06-22 18:03:14 -0700186 return srManager.getTaggedVlanId(newLocation).contains(hostVlanId) ||
Charles Chand9265a32017-06-16 15:19:24 -0700187 (oldAssignedVlan != null && newAssignedVlan != null &&
188 // Host is untagged and the new location has the same assigned vlan
189 oldAssignedVlan.equals(newAssignedVlan));
190 })) {
Charles Chan3ed34d82017-06-22 18:03:14 -0700191 processBridgingRule(prevLocation.deviceId(), prevLocation.port(), hostMac, hostVlanId, true);
Charles Chand9265a32017-06-16 15:19:24 -0700192 }
193
194 // Remove routing rules for unchanged IPs if none of the subnet of new location contains
195 // the IP. Otherwise, do not remove and let the adding part update the old flow
196 Sets.intersection(prevIps, newIps).forEach(ip -> {
197 if (newLocations.stream().noneMatch(newLocation ->
198 srManager.deviceConfiguration.inSameSubnet(newLocation, ip))) {
Charles Chan3ed34d82017-06-22 18:03:14 -0700199 processRoutingRule(prevLocation.deviceId(), prevLocation.port(), hostMac, hostVlanId,
Charles Chand9265a32017-06-16 15:19:24 -0700200 ip, true);
201 }
Charles Chanc22cef32016-04-29 14:38:22 -0700202 });
Charles Chand9265a32017-06-16 15:19:24 -0700203 });
204
205 // For each new location, add all new IPs.
206 Sets.difference(newLocations, prevLocations).forEach(newLocation -> {
Charles Chan3ed34d82017-06-22 18:03:14 -0700207 processBridgingRule(newLocation.deviceId(), newLocation.port(), hostMac, hostVlanId, false);
Charles Chand9265a32017-06-16 15:19:24 -0700208 newIps.forEach(ip ->
Charles Chan3ed34d82017-06-22 18:03:14 -0700209 processRoutingRule(newLocation.deviceId(), newLocation.port(), hostMac, hostVlanId,
Charles Chand9265a32017-06-16 15:19:24 -0700210 ip, false)
211 );
212 });
213
214 // For each unchanged location, add new IPs and remove old IPs.
215 Sets.intersection(newLocations, prevLocations).forEach(unchangedLocation -> {
216 Sets.difference(prevIps, newIps).forEach(ip ->
Charles Chan3ed34d82017-06-22 18:03:14 -0700217 processRoutingRule(unchangedLocation.deviceId(), unchangedLocation.port(), hostMac,
218 hostVlanId, ip, true)
Charles Chand9265a32017-06-16 15:19:24 -0700219 );
220
221 Sets.difference(newIps, prevIps).forEach(ip ->
Charles Chan3ed34d82017-06-22 18:03:14 -0700222 processRoutingRule(unchangedLocation.deviceId(), unchangedLocation.port(), hostMac,
223 hostVlanId, ip, false)
Charles Chand9265a32017-06-16 15:19:24 -0700224 );
225 });
Charles Chanc22cef32016-04-29 14:38:22 -0700226 }
227
Charles Chand9265a32017-06-16 15:19:24 -0700228 void processHostUpdatedEvent(HostEvent event) {
Charles Chanc22cef32016-04-29 14:38:22 -0700229 MacAddress mac = event.subject().mac();
230 VlanId vlanId = event.subject().vlan();
Charles Chand9265a32017-06-16 15:19:24 -0700231 Set<HostLocation> locations = event.subject().locations();
Charles Chanc22cef32016-04-29 14:38:22 -0700232 Set<IpAddress> prevIps = event.prevSubject().ipAddresses();
Charles Chanc22cef32016-04-29 14:38:22 -0700233 Set<IpAddress> newIps = event.subject().ipAddresses();
Saurav Dasf9332192017-02-18 14:05:44 -0800234 log.info("Host {}/{} is updated", mac, vlanId);
Charles Chanc22cef32016-04-29 14:38:22 -0700235
Charles Chand9265a32017-06-16 15:19:24 -0700236 locations.forEach(location -> {
237 Sets.difference(prevIps, newIps).forEach(ip ->
238 processRoutingRule(location.deviceId(), location.port(), mac, vlanId, ip, true)
239 );
240 Sets.difference(newIps, prevIps).forEach(ip ->
241 processRoutingRule(location.deviceId(), location.port(), mac, vlanId, ip, false)
242 );
243 });
Charles Chanc22cef32016-04-29 14:38:22 -0700244 }
245
246 /**
Charles Chan18fa4252017-02-08 16:10:40 -0800247 * Generates a forwarding objective builder for bridging rules.
248 * <p>
249 * The forwarding objective bridges packets destined to a given MAC to
250 * given port on given device.
Charles Chanc22cef32016-04-29 14:38:22 -0700251 *
252 * @param deviceId Device that host attaches to
253 * @param mac MAC address of the host
Charles Chan90772a72017-02-08 15:52:08 -0800254 * @param hostVlanId VLAN ID of the host
Charles Chanc22cef32016-04-29 14:38:22 -0700255 * @param outport Port that host attaches to
Saurav Das2cb38292017-03-29 19:09:17 -0700256 * @param revoke true if forwarding objective is meant to revoke forwarding rule
Charles Chanc22cef32016-04-29 14:38:22 -0700257 * @return Forwarding objective builder
258 */
Charles Chand9265a32017-06-16 15:19:24 -0700259 ForwardingObjective.Builder bridgingFwdObjBuilder(
Charles Chan90772a72017-02-08 15:52:08 -0800260 DeviceId deviceId, MacAddress mac, VlanId hostVlanId,
Saurav Das2cb38292017-03-29 19:09:17 -0700261 PortNumber outport, boolean revoke) {
Charles Chan90772a72017-02-08 15:52:08 -0800262 ConnectPoint connectPoint = new ConnectPoint(deviceId, outport);
263 VlanId untaggedVlan = srManager.getUntaggedVlanId(connectPoint);
264 Set<VlanId> taggedVlans = srManager.getTaggedVlanId(connectPoint);
265 VlanId nativeVlan = srManager.getNativeVlanId(connectPoint);
Charles Chan1eaf4802016-04-18 13:44:03 -0700266
Charles Chan90772a72017-02-08 15:52:08 -0800267 // Create host selector
Charles Chan1eaf4802016-04-18 13:44:03 -0700268 TrafficSelector.Builder sbuilder = DefaultTrafficSelector.builder();
269 sbuilder.matchEthDst(mac);
Charles Chan1eaf4802016-04-18 13:44:03 -0700270
Charles Chan90772a72017-02-08 15:52:08 -0800271 // Create host treatment
Charles Chan1eaf4802016-04-18 13:44:03 -0700272 TrafficTreatment.Builder tbuilder = DefaultTrafficTreatment.builder();
Charles Chan1eaf4802016-04-18 13:44:03 -0700273 tbuilder.immediate().setOutput(outport);
274
Charles Chan90772a72017-02-08 15:52:08 -0800275 // Create host meta
276 TrafficSelector.Builder mbuilder = DefaultTrafficSelector.builder();
277
278 // Adjust the selector, treatment and meta according to VLAN configuration
279 if (taggedVlans.contains(hostVlanId)) {
280 sbuilder.matchVlanId(hostVlanId);
281 mbuilder.matchVlanId(hostVlanId);
282 } else if (hostVlanId.equals(VlanId.NONE)) {
283 if (untaggedVlan != null) {
284 sbuilder.matchVlanId(untaggedVlan);
285 mbuilder.matchVlanId(untaggedVlan);
286 tbuilder.immediate().popVlan();
287 } else if (nativeVlan != null) {
288 sbuilder.matchVlanId(nativeVlan);
289 mbuilder.matchVlanId(nativeVlan);
290 tbuilder.immediate().popVlan();
291 } else {
Charles Chan184e0242017-05-26 14:23:58 -0700292 log.warn("Untagged host {}/{} is not allowed on {} without untagged or native" +
293 "vlan config", mac, hostVlanId, connectPoint);
294 return null;
Charles Chan90772a72017-02-08 15:52:08 -0800295 }
296 } else {
297 log.warn("Tagged host {}/{} is not allowed on {} without VLAN listed in tagged vlan",
298 mac, hostVlanId, connectPoint);
299 return null;
300 }
Charles Chan1eaf4802016-04-18 13:44:03 -0700301
302 // All forwarding is via Groups. Drivers can re-purpose to flow-actions if needed.
Saurav Das2cb38292017-03-29 19:09:17 -0700303 // If the objective is to revoke an existing rule, and for some reason
304 // the next-objective does not exist, then a new one should not be created
Charles Chan1eaf4802016-04-18 13:44:03 -0700305 int portNextObjId = srManager.getPortNextObjectiveId(deviceId, outport,
Saurav Das2cb38292017-03-29 19:09:17 -0700306 tbuilder.build(), mbuilder.build(), !revoke);
Saurav Das07c74602016-04-27 18:35:50 -0700307 if (portNextObjId == -1) {
Charles Chan90772a72017-02-08 15:52:08 -0800308 // Warning log will come from getPortNextObjective method
Saurav Das07c74602016-04-27 18:35:50 -0700309 return null;
310 }
Charles Chan1eaf4802016-04-18 13:44:03 -0700311
312 return DefaultForwardingObjective.builder()
313 .withFlag(ForwardingObjective.Flag.SPECIFIC)
314 .withSelector(sbuilder.build())
315 .nextStep(portNextObjId)
316 .withPriority(100)
317 .fromApp(srManager.appId)
318 .makePermanent();
319 }
320
Charles Chanc22cef32016-04-29 14:38:22 -0700321 /**
Charles Chan9595e6a2017-06-15 19:25:25 -0700322 * Populate or revoke a bridging rule on given deviceId that matches given mac, given vlan and
323 * output to given port.
324 *
325 * @param deviceId device ID
326 * @param port port
327 * @param mac mac address
328 * @param vlanId VLAN ID
329 * @param revoke true to revoke the rule; false to populate
330 */
331 private void processBridgingRule(DeviceId deviceId, PortNumber port, MacAddress mac,
332 VlanId vlanId, boolean revoke) {
333 log.debug("{} bridging entry for host {}/{} at {}:{}", revoke ? "Revoking" : "Populating",
334 mac, vlanId, deviceId, port);
335
336 ForwardingObjective.Builder fob = bridgingFwdObjBuilder(deviceId, mac, vlanId, port, revoke);
337 if (fob == null) {
338 log.warn("Fail to build fwd obj for host {}/{}. Abort.", mac, vlanId);
339 return;
340 }
341
342 ObjectiveContext context = new DefaultObjectiveContext(
343 (objective) -> log.debug("Brigding rule for {}/{} {}", mac, vlanId,
344 revoke ? "revoked" : "populated"),
345 (objective, error) -> log.warn("Failed to {} bridging rule for {}/{}: {}",
346 revoke ? "revoked" : "populated", mac, vlanId, error));
347 flowObjectiveService.forward(deviceId, revoke ? fob.remove(context) : fob.add(context));
348 }
349
350 /**
351 * Populate or revoke a routing rule on given deviceId that matches given ip,
352 * set destination mac to given mac, set vlan to given vlan and output to given port.
353 *
354 * @param deviceId device ID
355 * @param port port
356 * @param mac mac address
357 * @param vlanId VLAN ID
358 * @param ip IP address
359 * @param revoke true to revoke the rule; false to populate
360 */
361 private void processRoutingRule(DeviceId deviceId, PortNumber port, MacAddress mac,
362 VlanId vlanId, IpAddress ip, boolean revoke) {
363 ConnectPoint location = new ConnectPoint(deviceId, port);
Charles Chand9265a32017-06-16 15:19:24 -0700364 if (!srManager.deviceConfiguration.inSameSubnet(location, ip)) {
365 log.info("{} is not included in the subnet config of {}/{}. Ignored.", ip, deviceId, port);
366 return;
Charles Chan9595e6a2017-06-15 19:25:25 -0700367 }
Charles Chan9595e6a2017-06-15 19:25:25 -0700368
Charles Chand9265a32017-06-16 15:19:24 -0700369 log.info("{} routing rule for {} at {}", revoke ? "Revoking" : "Populating", ip, location);
370 if (revoke) {
371 srManager.routingRulePopulator.revokeRoute(deviceId, ip.toIpPrefix(), mac, vlanId, port);
372 } else {
373 srManager.routingRulePopulator.populateRoute(deviceId, ip.toIpPrefix(), mac, vlanId, port);
Charles Chan370a65b2016-05-10 17:29:47 -0700374 }
Charles Chan370a65b2016-05-10 17:29:47 -0700375 }
Charles Chan1eaf4802016-04-18 13:44:03 -0700376}