blob: 908a33943b17295cba0b44224721cc9dcf6a0a81 [file] [log] [blame]
Charles Chand2990362016-04-18 13:44:03 -07001/*
Brian O'Connora09fe5b2017-08-03 21:12:30 -07002 * Copyright 2016-present Open Networking Foundation
Charles Chand2990362016-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 Chand2990362016-04-18 13:44:03 -070019import org.onlab.packet.IpAddress;
Jonghwan Hyun5f1def82017-08-25 17:48:36 -070020import org.onlab.packet.IpPrefix;
Charles Chand2990362016-04-18 13:44:03 -070021import org.onlab.packet.MacAddress;
22import org.onlab.packet.VlanId;
Charles Chan59cc16d2017-02-02 16:20:42 -080023import org.onosproject.net.ConnectPoint;
Charles Chand2990362016-04-18 13:44:03 -070024import org.onosproject.net.DeviceId;
Charles Chan6ea94fc2016-05-10 17:29:47 -070025import org.onosproject.net.Host;
Charles Chan93e71ba2016-04-29 14:38:22 -070026import org.onosproject.net.HostLocation;
Charles Chand2990362016-04-18 13:44:03 -070027import org.onosproject.net.PortNumber;
28import org.onosproject.net.flow.DefaultTrafficSelector;
29import org.onosproject.net.flow.DefaultTrafficTreatment;
30import org.onosproject.net.flow.TrafficSelector;
31import org.onosproject.net.flow.TrafficTreatment;
32import org.onosproject.net.flowobjective.DefaultForwardingObjective;
33import org.onosproject.net.flowobjective.DefaultObjectiveContext;
34import org.onosproject.net.flowobjective.FlowObjectiveService;
35import org.onosproject.net.flowobjective.ForwardingObjective;
36import org.onosproject.net.flowobjective.ObjectiveContext;
37import org.onosproject.net.host.HostEvent;
Charles Chanba90df12017-11-30 15:37:50 -080038import org.onosproject.net.host.HostLocationProbingService.ProbeMode;
Charles Chand2990362016-04-18 13:44:03 -070039import org.onosproject.net.host.HostService;
40import org.slf4j.Logger;
41import org.slf4j.LoggerFactory;
42
Saurav Das018605f2017-02-18 14:05:44 -080043import com.google.common.collect.Sets;
Charles Chan65238242017-06-22 18:03:14 -070044
Saurav Dase9c8971e2018-01-18 12:07:33 -080045import java.util.HashSet;
Charles Chan65238242017-06-22 18:03:14 -070046import java.util.Optional;
Charles Chand2990362016-04-18 13:44:03 -070047import java.util.Set;
Charles Chanba90df12017-11-30 15:37:50 -080048import java.util.concurrent.Executors;
49import java.util.concurrent.ScheduledExecutorService;
50import java.util.concurrent.TimeUnit;
Charles Chanf9a52702017-06-16 15:19:24 -070051import java.util.stream.Collectors;
52
53import static com.google.common.base.Preconditions.checkArgument;
Charles Chand2990362016-04-18 13:44:03 -070054
55/**
56 * Handles host-related events.
57 */
58public class HostHandler {
59 private static final Logger log = LoggerFactory.getLogger(HostHandler.class);
Charles Chanba90df12017-11-30 15:37:50 -080060 static final int HOST_MOVED_DELAY_MS = 1000;
61
Charles Chan2e2e3402017-06-19 14:00:53 -070062 protected final SegmentRoutingManager srManager;
Charles Chand2990362016-04-18 13:44:03 -070063 private HostService hostService;
64 private FlowObjectiveService flowObjectiveService;
65
66 /**
67 * Constructs the HostHandler.
68 *
69 * @param srManager Segment Routing manager
70 */
Charles Chanf9a52702017-06-16 15:19:24 -070071 HostHandler(SegmentRoutingManager srManager) {
Charles Chand2990362016-04-18 13:44:03 -070072 this.srManager = srManager;
Charles Chand2990362016-04-18 13:44:03 -070073 hostService = srManager.hostService;
74 flowObjectiveService = srManager.flowObjectiveService;
75 }
76
Charles Chan03a73e02016-10-24 14:52:01 -070077 protected void init(DeviceId devId) {
Charles Chanf9a52702017-06-16 15:19:24 -070078 hostService.getHosts().forEach(host ->
79 host.locations().stream()
Charles Chanba90df12017-11-30 15:37:50 -080080 .filter(location -> location.deviceId().equals(devId) ||
81 location.deviceId().equals(srManager.getPairDeviceId(devId).orElse(null)))
Charles Chanf9a52702017-06-16 15:19:24 -070082 .forEach(location -> processHostAddedAtLocation(host, location))
83 );
Charles Chan93e71ba2016-04-29 14:38:22 -070084 }
Charles Chand2990362016-04-18 13:44:03 -070085
Charles Chanf9a52702017-06-16 15:19:24 -070086 void processHostAddedEvent(HostEvent event) {
Charles Chan35fd1a72016-06-13 18:54:31 -070087 processHostAdded(event.subject());
Charles Chan93e71ba2016-04-29 14:38:22 -070088 }
89
Charles Chanf9a52702017-06-16 15:19:24 -070090 private void processHostAdded(Host host) {
91 host.locations().forEach(location -> processHostAddedAtLocation(host, location));
Saurav Dase9c8971e2018-01-18 12:07:33 -080092 // ensure dual-homed host locations have viable uplinks
93 if (host.locations().size() > 1) {
94 host.locations().forEach(loc -> {
95 if (srManager.mastershipService.isLocalMaster(loc.deviceId())) {
96 srManager.linkHandler.checkUplinksForDualHomedHosts(loc);
97 }
98 });
99 }
Charles Chand2990362016-04-18 13:44:03 -0700100 }
101
Charles Chanf9a52702017-06-16 15:19:24 -0700102 void processHostAddedAtLocation(Host host, HostLocation location) {
103 checkArgument(host.locations().contains(location), "{} is not a location of {}", location, host);
104
Charles Chan9ff637e2017-09-12 18:57:47 -0700105 MacAddress hostMac = host.mac();
106 VlanId hostVlanId = host.vlan();
Charles Chanf9a52702017-06-16 15:19:24 -0700107 Set<HostLocation> locations = host.locations();
108 Set<IpAddress> ips = host.ipAddresses();
Charles Chan9ff637e2017-09-12 18:57:47 -0700109 log.info("Host {}/{} is added at {}", hostMac, hostVlanId, locations);
Charles Chanf9a52702017-06-16 15:19:24 -0700110
Charles Chan9ff637e2017-09-12 18:57:47 -0700111 if (srManager.isMasterOf(location)) {
112 processBridgingRule(location.deviceId(), location.port(), hostMac, hostVlanId, false);
113 ips.forEach(ip ->
114 processRoutingRule(location.deviceId(), location.port(), hostMac, hostVlanId, ip, false)
115 );
116 }
117
118 // Use the pair link temporarily before the second location of a dual-homed host shows up.
119 // This do not affect single-homed hosts since the flow will be blocked in
120 // processBridgingRule or processRoutingRule due to VLAN or IP mismatch respectively
121 srManager.getPairDeviceId(location.deviceId()).ifPresent(pairDeviceId -> {
122 if (srManager.mastershipService.isLocalMaster(pairDeviceId) &&
123 host.locations().stream().noneMatch(l -> l.deviceId().equals(pairDeviceId))) {
124 srManager.getPairLocalPorts(pairDeviceId).ifPresent(pairRemotePort -> {
125 // NOTE: Since the pairLocalPort is trunk port, use assigned vlan of original port
126 // when the host is untagged
127 VlanId vlanId = Optional.ofNullable(srManager.getInternalVlanId(location)).orElse(hostVlanId);
128
129 processBridgingRule(pairDeviceId, pairRemotePort, hostMac, vlanId, false);
130 ips.forEach(ip -> processRoutingRule(pairDeviceId, pairRemotePort, hostMac, vlanId,
131 ip, false));
Charles Chanba90df12017-11-30 15:37:50 -0800132
133 if (srManager.activeProbing) {
134 probe(host, location, pairDeviceId, pairRemotePort);
135 }
Charles Chan9ff637e2017-09-12 18:57:47 -0700136 });
137 }
138 });
Charles Chanf9a52702017-06-16 15:19:24 -0700139 }
140
141 void processHostRemovedEvent(HostEvent event) {
Charles Chan35fd1a72016-06-13 18:54:31 -0700142 processHostRemoved(event.subject());
143 }
144
Charles Chanf9a52702017-06-16 15:19:24 -0700145 private void processHostRemoved(Host host) {
Charles Chan65238242017-06-22 18:03:14 -0700146 MacAddress hostMac = host.mac();
147 VlanId hostVlanId = host.vlan();
Charles Chanf9a52702017-06-16 15:19:24 -0700148 Set<HostLocation> locations = host.locations();
Charles Chan35fd1a72016-06-13 18:54:31 -0700149 Set<IpAddress> ips = host.ipAddresses();
Charles Chan65238242017-06-22 18:03:14 -0700150 log.info("Host {}/{} is removed from {}", hostMac, hostVlanId, locations);
Charles Chan93e71ba2016-04-29 14:38:22 -0700151
Charles Chan23686832017-08-23 14:46:43 -0700152 locations.forEach(location -> {
153 if (srManager.isMasterOf(location)) {
154 processBridgingRule(location.deviceId(), location.port(), hostMac, hostVlanId, true);
155 ips.forEach(ip ->
156 processRoutingRule(location.deviceId(), location.port(), hostMac, hostVlanId, ip, true)
157 );
158 }
Charles Chan65238242017-06-22 18:03:14 -0700159
160 // Also remove redirection flows on the pair device if exists.
161 Optional<DeviceId> pairDeviceId = srManager.getPairDeviceId(location.deviceId());
162 Optional<PortNumber> pairLocalPort = srManager.getPairLocalPorts(location.deviceId());
Charles Chan23686832017-08-23 14:46:43 -0700163 if (pairDeviceId.isPresent() && pairLocalPort.isPresent() &&
164 srManager.mastershipService.isLocalMaster(pairDeviceId.get())) {
Charles Chan65238242017-06-22 18:03:14 -0700165 // NOTE: Since the pairLocalPort is trunk port, use assigned vlan of original port
166 // when the host is untagged
167 VlanId vlanId = Optional.ofNullable(srManager.getInternalVlanId(location)).orElse(hostVlanId);
168
169 processBridgingRule(pairDeviceId.get(), pairLocalPort.get(), hostMac, vlanId, true);
170 ips.forEach(ip ->
171 processRoutingRule(pairDeviceId.get(), pairLocalPort.get(), hostMac, vlanId,
172 ip, true));
173 }
Charles Chanf9a52702017-06-16 15:19:24 -0700174 });
Charles Chan93e71ba2016-04-29 14:38:22 -0700175 }
176
Charles Chanf9a52702017-06-16 15:19:24 -0700177 void processHostMovedEvent(HostEvent event) {
Charles Chan65238242017-06-22 18:03:14 -0700178 MacAddress hostMac = event.subject().mac();
179 VlanId hostVlanId = event.subject().vlan();
Charles Chanf9a52702017-06-16 15:19:24 -0700180 Set<HostLocation> prevLocations = event.prevSubject().locations();
Charles Chan93e71ba2016-04-29 14:38:22 -0700181 Set<IpAddress> prevIps = event.prevSubject().ipAddresses();
Charles Chanf9a52702017-06-16 15:19:24 -0700182 Set<HostLocation> newLocations = event.subject().locations();
Charles Chan93e71ba2016-04-29 14:38:22 -0700183 Set<IpAddress> newIps = event.subject().ipAddresses();
Charles Chan93e71ba2016-04-29 14:38:22 -0700184
Charles Chanba90df12017-11-30 15:37:50 -0800185 // FIXME: Delay event handling a little bit to wait for the previous redirection flows to be completed
186 // The permanent solution would be introducing CompletableFuture and wait for it
187 if (prevLocations.size() == 1 && newLocations.size() == 2) {
188 log.debug("Delay event handling when host {}/{} moves from 1 to 2 locations", hostMac, hostVlanId);
189 ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();
190 executorService.schedule(() ->
191 processHostMoved(hostMac, hostVlanId, prevLocations, prevIps, newLocations, newIps),
192 HOST_MOVED_DELAY_MS, TimeUnit.MILLISECONDS);
193 } else {
194 processHostMoved(hostMac, hostVlanId, prevLocations, prevIps, newLocations, newIps);
195 }
196 }
197
198 private void processHostMoved(MacAddress hostMac, VlanId hostVlanId, Set<HostLocation> prevLocations,
199 Set<IpAddress> prevIps, Set<HostLocation> newLocations, Set<IpAddress> newIps) {
200 log.info("Host {}/{} is moved from {} to {}", hostMac, hostVlanId, prevLocations, newLocations);
Charles Chanf9a52702017-06-16 15:19:24 -0700201 Set<DeviceId> newDeviceIds = newLocations.stream().map(HostLocation::deviceId)
202 .collect(Collectors.toSet());
Charles Chan93e71ba2016-04-29 14:38:22 -0700203
Charles Chanf9a52702017-06-16 15:19:24 -0700204 // For each old location
Charles Chan23686832017-08-23 14:46:43 -0700205 Sets.difference(prevLocations, newLocations).stream().filter(srManager::isMasterOf)
Charles Chan9f08b102017-08-09 16:50:15 -0700206 .forEach(prevLocation -> {
Charles Chan65238242017-06-22 18:03:14 -0700207 // Remove routing rules for old IPs
208 Sets.difference(prevIps, newIps).forEach(ip ->
209 processRoutingRule(prevLocation.deviceId(), prevLocation.port(), hostMac, hostVlanId,
210 ip, true)
211 );
212
213 // Redirect the flows to pair link if configured
214 // Note: Do not continue removing any rule
215 Optional<DeviceId> pairDeviceId = srManager.getPairDeviceId(prevLocation.deviceId());
216 Optional<PortNumber> pairLocalPort = srManager.getPairLocalPorts(prevLocation.deviceId());
217 if (pairDeviceId.isPresent() && pairLocalPort.isPresent() && newLocations.stream()
218 .anyMatch(location -> location.deviceId().equals(pairDeviceId.get()))) {
219 // NOTE: Since the pairLocalPort is trunk port, use assigned vlan of original port
220 // when the host is untagged
221 VlanId vlanId = Optional.ofNullable(srManager.getInternalVlanId(prevLocation)).orElse(hostVlanId);
222
223 processBridgingRule(prevLocation.deviceId(), pairLocalPort.get(), hostMac, vlanId, false);
224 newIps.forEach(ip ->
225 processRoutingRule(prevLocation.deviceId(), pairLocalPort.get(), hostMac, vlanId,
226 ip, false));
227 return;
228 }
Charles Chanf9a52702017-06-16 15:19:24 -0700229
230 // Remove bridging rule and routing rules for unchanged IPs if the host moves from a switch to another.
231 // Otherwise, do not remove and let the adding part update the old flow
232 if (!newDeviceIds.contains(prevLocation.deviceId())) {
Charles Chan65238242017-06-22 18:03:14 -0700233 processBridgingRule(prevLocation.deviceId(), prevLocation.port(), hostMac, hostVlanId, true);
Charles Chanf9a52702017-06-16 15:19:24 -0700234 Sets.intersection(prevIps, newIps).forEach(ip ->
Charles Chan65238242017-06-22 18:03:14 -0700235 processRoutingRule(prevLocation.deviceId(), prevLocation.port(), hostMac, hostVlanId,
Charles Chanf9a52702017-06-16 15:19:24 -0700236 ip, true)
237 );
238 }
239
240 // Remove bridging rules if new interface vlan is different from old interface vlan
241 // Otherwise, do not remove and let the adding part update the old flow
242 if (newLocations.stream().noneMatch(newLocation -> {
243 VlanId oldAssignedVlan = srManager.getInternalVlanId(prevLocation);
244 VlanId newAssignedVlan = srManager.getInternalVlanId(newLocation);
245 // Host is tagged and the new location has the host vlan in vlan-tagged
Charles Chan65238242017-06-22 18:03:14 -0700246 return srManager.getTaggedVlanId(newLocation).contains(hostVlanId) ||
Charles Chanf9a52702017-06-16 15:19:24 -0700247 (oldAssignedVlan != null && newAssignedVlan != null &&
248 // Host is untagged and the new location has the same assigned vlan
249 oldAssignedVlan.equals(newAssignedVlan));
250 })) {
Charles Chan65238242017-06-22 18:03:14 -0700251 processBridgingRule(prevLocation.deviceId(), prevLocation.port(), hostMac, hostVlanId, true);
Charles Chanf9a52702017-06-16 15:19:24 -0700252 }
253
254 // Remove routing rules for unchanged IPs if none of the subnet of new location contains
255 // the IP. Otherwise, do not remove and let the adding part update the old flow
256 Sets.intersection(prevIps, newIps).forEach(ip -> {
257 if (newLocations.stream().noneMatch(newLocation ->
258 srManager.deviceConfiguration.inSameSubnet(newLocation, ip))) {
Charles Chan65238242017-06-22 18:03:14 -0700259 processRoutingRule(prevLocation.deviceId(), prevLocation.port(), hostMac, hostVlanId,
Charles Chanf9a52702017-06-16 15:19:24 -0700260 ip, true);
261 }
Charles Chan93e71ba2016-04-29 14:38:22 -0700262 });
Charles Chanf9a52702017-06-16 15:19:24 -0700263 });
264
265 // For each new location, add all new IPs.
Charles Chan23686832017-08-23 14:46:43 -0700266 Sets.difference(newLocations, prevLocations).stream().filter(srManager::isMasterOf)
Charles Chan9f08b102017-08-09 16:50:15 -0700267 .forEach(newLocation -> {
Charles Chan65238242017-06-22 18:03:14 -0700268 processBridgingRule(newLocation.deviceId(), newLocation.port(), hostMac, hostVlanId, false);
Charles Chanf9a52702017-06-16 15:19:24 -0700269 newIps.forEach(ip ->
Charles Chan65238242017-06-22 18:03:14 -0700270 processRoutingRule(newLocation.deviceId(), newLocation.port(), hostMac, hostVlanId,
Charles Chanf9a52702017-06-16 15:19:24 -0700271 ip, false)
272 );
273 });
274
275 // For each unchanged location, add new IPs and remove old IPs.
Charles Chan23686832017-08-23 14:46:43 -0700276 Sets.intersection(newLocations, prevLocations).stream().filter(srManager::isMasterOf)
Charles Chan9f08b102017-08-09 16:50:15 -0700277 .forEach(unchangedLocation -> {
Charles Chanf9a52702017-06-16 15:19:24 -0700278 Sets.difference(prevIps, newIps).forEach(ip ->
Charles Chan65238242017-06-22 18:03:14 -0700279 processRoutingRule(unchangedLocation.deviceId(), unchangedLocation.port(), hostMac,
280 hostVlanId, ip, true)
Charles Chanf9a52702017-06-16 15:19:24 -0700281 );
282
283 Sets.difference(newIps, prevIps).forEach(ip ->
Charles Chan65238242017-06-22 18:03:14 -0700284 processRoutingRule(unchangedLocation.deviceId(), unchangedLocation.port(), hostMac,
285 hostVlanId, ip, false)
Charles Chanf9a52702017-06-16 15:19:24 -0700286 );
287 });
Saurav Dase9c8971e2018-01-18 12:07:33 -0800288
289 // ensure dual-homed host locations have viable uplinks
290 if (newLocations.size() > prevLocations.size()) {
291 newLocations.forEach(loc -> {
292 if (srManager.mastershipService.isLocalMaster(loc.deviceId())) {
293 srManager.linkHandler.checkUplinksForDualHomedHosts(loc);
294 }
295 });
296 }
Charles Chan93e71ba2016-04-29 14:38:22 -0700297 }
298
Charles Chanf9a52702017-06-16 15:19:24 -0700299 void processHostUpdatedEvent(HostEvent event) {
Charles Chanba90df12017-11-30 15:37:50 -0800300 Host host = event.subject();
301 MacAddress hostMac = host.mac();
302 VlanId hostVlanId = host.vlan();
303 Set<HostLocation> locations = host.locations();
Charles Chan93e71ba2016-04-29 14:38:22 -0700304 Set<IpAddress> prevIps = event.prevSubject().ipAddresses();
Charles Chanba90df12017-11-30 15:37:50 -0800305 Set<IpAddress> newIps = host.ipAddresses();
Charles Chan0ad79082017-11-20 08:46:24 -0800306 log.info("Host {}/{} is updated", hostMac, hostVlanId);
Charles Chan93e71ba2016-04-29 14:38:22 -0700307
Charles Chan23686832017-08-23 14:46:43 -0700308 locations.stream().filter(srManager::isMasterOf).forEach(location -> {
Charles Chanf9a52702017-06-16 15:19:24 -0700309 Sets.difference(prevIps, newIps).forEach(ip ->
Charles Chan0ad79082017-11-20 08:46:24 -0800310 processRoutingRule(location.deviceId(), location.port(), hostMac, hostVlanId, ip, true)
Charles Chanf9a52702017-06-16 15:19:24 -0700311 );
312 Sets.difference(newIps, prevIps).forEach(ip ->
Charles Chan0ad79082017-11-20 08:46:24 -0800313 processRoutingRule(location.deviceId(), location.port(), hostMac, hostVlanId, ip, false)
Charles Chanf9a52702017-06-16 15:19:24 -0700314 );
315 });
Charles Chan0ad79082017-11-20 08:46:24 -0800316
317 // Use the pair link temporarily before the second location of a dual-homed host shows up.
318 // This do not affect single-homed hosts since the flow will be blocked in
319 // processBridgingRule or processRoutingRule due to VLAN or IP mismatch respectively
Charles Chanba90df12017-11-30 15:37:50 -0800320 locations.forEach(location ->
Charles Chan0ad79082017-11-20 08:46:24 -0800321 srManager.getPairDeviceId(location.deviceId()).ifPresent(pairDeviceId -> {
322 if (srManager.mastershipService.isLocalMaster(pairDeviceId) &&
323 locations.stream().noneMatch(l -> l.deviceId().equals(pairDeviceId))) {
Charles Chanba90df12017-11-30 15:37:50 -0800324 Set<IpAddress> ipsToAdd = Sets.difference(newIps, prevIps);
325 Set<IpAddress> ipsToRemove = Sets.difference(prevIps, newIps);
326
Charles Chan0ad79082017-11-20 08:46:24 -0800327 srManager.getPairLocalPorts(pairDeviceId).ifPresent(pairRemotePort -> {
328 // NOTE: Since the pairLocalPort is trunk port, use assigned vlan of original port
329 // when the host is untagged
330 VlanId vlanId = Optional.ofNullable(srManager.getInternalVlanId(location)).orElse(hostVlanId);
331
Charles Chanba90df12017-11-30 15:37:50 -0800332 ipsToRemove.forEach(ip ->
Charles Chan0ad79082017-11-20 08:46:24 -0800333 processRoutingRule(pairDeviceId, pairRemotePort, hostMac, vlanId, ip, true)
334 );
Charles Chanba90df12017-11-30 15:37:50 -0800335 ipsToAdd.forEach(ip ->
Charles Chan0ad79082017-11-20 08:46:24 -0800336 processRoutingRule(pairDeviceId, pairRemotePort, hostMac, vlanId, ip, false)
337 );
Charles Chanba90df12017-11-30 15:37:50 -0800338
339 if (srManager.activeProbing) {
340 probe(host, location, pairDeviceId, pairRemotePort);
341 }
Charles Chan0ad79082017-11-20 08:46:24 -0800342 });
343 }
Charles Chanba90df12017-11-30 15:37:50 -0800344 })
345 );
346 }
347
348 /**
349 * When a non-pair port comes up, probe each host on the pair device if
350 * (1) the host is tagged and the tagged vlan of current port contains host vlan; or
351 * (2) the host is untagged and the internal vlan is the same on the host port and current port.
352 *
353 * @param cp connect point
354 */
355 void processPortUp(ConnectPoint cp) {
356 if (cp.port().equals(srManager.getPairLocalPorts(cp.deviceId()).orElse(null))) {
357 return;
358 }
359 if (srManager.activeProbing) {
360 srManager.getPairDeviceId(cp.deviceId())
361 .ifPresent(pairDeviceId -> srManager.hostService.getConnectedHosts(pairDeviceId).stream()
362 .filter(host -> isHostInVlanOfPort(host, pairDeviceId, cp))
363 .forEach(host -> srManager.probingService.probeHostLocation(host, cp, ProbeMode.DISCOVER))
364 );
365 }
366 }
367
368 /**
369 * Checks if given host located on given device id matches VLAN config of current port.
370 *
371 * @param host host to check
372 * @param deviceId device id to check
373 * @param cp current connect point
374 * @return true if the host located at deviceId matches the VLAN config on cp
375 */
376 private boolean isHostInVlanOfPort(Host host, DeviceId deviceId, ConnectPoint cp) {
377 VlanId internalVlan = srManager.getInternalVlanId(cp);
378 Set<VlanId> taggedVlan = srManager.getTaggedVlanId(cp);
379
380 return taggedVlan.contains(host.vlan()) ||
381 (internalVlan != null && host.locations().stream()
382 .filter(l -> l.deviceId().equals(deviceId))
383 .map(srManager::getInternalVlanId)
384 .anyMatch(internalVlan::equals));
385 }
386
387 /**
388 * Send a probe on all locations with the same VLAN on pair device, excluding pair port.
389 *
390 * @param host host to probe
391 * @param location newly discovered host location
392 * @param pairDeviceId pair device id
393 * @param pairRemotePort pair remote port
394 */
395 private void probe(Host host, ConnectPoint location, DeviceId pairDeviceId, PortNumber pairRemotePort) {
396 VlanId vlanToProbe = host.vlan().equals(VlanId.NONE) ?
397 srManager.getInternalVlanId(location) : host.vlan();
398 srManager.interfaceService.getInterfaces().stream()
399 .filter(i -> i.vlanTagged().contains(vlanToProbe) ||
400 i.vlanUntagged().equals(vlanToProbe) ||
401 i.vlanNative().equals(vlanToProbe))
402 .filter(i -> i.connectPoint().deviceId().equals(pairDeviceId))
403 .filter(i -> !i.connectPoint().port().equals(pairRemotePort))
404 .forEach(i -> {
405 log.debug("Probing host {} on pair device {}", host.id(), i.connectPoint());
406 srManager.probingService.probeHostLocation(host, i.connectPoint(), ProbeMode.DISCOVER);
407 });
Charles Chan93e71ba2016-04-29 14:38:22 -0700408 }
409
410 /**
Charles Chanf6ec1532017-02-08 16:10:40 -0800411 * Generates a forwarding objective builder for bridging rules.
412 * <p>
413 * The forwarding objective bridges packets destined to a given MAC to
414 * given port on given device.
Charles Chan93e71ba2016-04-29 14:38:22 -0700415 *
416 * @param deviceId Device that host attaches to
417 * @param mac MAC address of the host
Charles Chan7ffd81f2017-02-08 15:52:08 -0800418 * @param hostVlanId VLAN ID of the host
Charles Chan93e71ba2016-04-29 14:38:22 -0700419 * @param outport Port that host attaches to
Saurav Das961beb22017-03-29 19:09:17 -0700420 * @param revoke true if forwarding objective is meant to revoke forwarding rule
Charles Chan93e71ba2016-04-29 14:38:22 -0700421 * @return Forwarding objective builder
422 */
Charles Chanf9a52702017-06-16 15:19:24 -0700423 ForwardingObjective.Builder bridgingFwdObjBuilder(
Charles Chan7ffd81f2017-02-08 15:52:08 -0800424 DeviceId deviceId, MacAddress mac, VlanId hostVlanId,
Saurav Das961beb22017-03-29 19:09:17 -0700425 PortNumber outport, boolean revoke) {
Charles Chan7ffd81f2017-02-08 15:52:08 -0800426 ConnectPoint connectPoint = new ConnectPoint(deviceId, outport);
427 VlanId untaggedVlan = srManager.getUntaggedVlanId(connectPoint);
428 Set<VlanId> taggedVlans = srManager.getTaggedVlanId(connectPoint);
429 VlanId nativeVlan = srManager.getNativeVlanId(connectPoint);
Charles Chand2990362016-04-18 13:44:03 -0700430
Charles Chan7ffd81f2017-02-08 15:52:08 -0800431 // Create host selector
Charles Chand2990362016-04-18 13:44:03 -0700432 TrafficSelector.Builder sbuilder = DefaultTrafficSelector.builder();
433 sbuilder.matchEthDst(mac);
Charles Chand2990362016-04-18 13:44:03 -0700434
Charles Chan7ffd81f2017-02-08 15:52:08 -0800435 // Create host treatment
Charles Chand2990362016-04-18 13:44:03 -0700436 TrafficTreatment.Builder tbuilder = DefaultTrafficTreatment.builder();
Charles Chand2990362016-04-18 13:44:03 -0700437 tbuilder.immediate().setOutput(outport);
438
Charles Chan7ffd81f2017-02-08 15:52:08 -0800439 // Create host meta
440 TrafficSelector.Builder mbuilder = DefaultTrafficSelector.builder();
441
442 // Adjust the selector, treatment and meta according to VLAN configuration
443 if (taggedVlans.contains(hostVlanId)) {
444 sbuilder.matchVlanId(hostVlanId);
445 mbuilder.matchVlanId(hostVlanId);
446 } else if (hostVlanId.equals(VlanId.NONE)) {
447 if (untaggedVlan != null) {
448 sbuilder.matchVlanId(untaggedVlan);
449 mbuilder.matchVlanId(untaggedVlan);
450 tbuilder.immediate().popVlan();
451 } else if (nativeVlan != null) {
452 sbuilder.matchVlanId(nativeVlan);
453 mbuilder.matchVlanId(nativeVlan);
454 tbuilder.immediate().popVlan();
455 } else {
Charles Chan3ca0b2e2017-05-26 14:23:58 -0700456 log.warn("Untagged host {}/{} is not allowed on {} without untagged or native" +
457 "vlan config", mac, hostVlanId, connectPoint);
458 return null;
Charles Chan7ffd81f2017-02-08 15:52:08 -0800459 }
460 } else {
461 log.warn("Tagged host {}/{} is not allowed on {} without VLAN listed in tagged vlan",
462 mac, hostVlanId, connectPoint);
463 return null;
464 }
Charles Chand2990362016-04-18 13:44:03 -0700465
466 // All forwarding is via Groups. Drivers can re-purpose to flow-actions if needed.
Saurav Das961beb22017-03-29 19:09:17 -0700467 // If the objective is to revoke an existing rule, and for some reason
468 // the next-objective does not exist, then a new one should not be created
Charles Chand2990362016-04-18 13:44:03 -0700469 int portNextObjId = srManager.getPortNextObjectiveId(deviceId, outport,
Saurav Das961beb22017-03-29 19:09:17 -0700470 tbuilder.build(), mbuilder.build(), !revoke);
Saurav Das59232cf2016-04-27 18:35:50 -0700471 if (portNextObjId == -1) {
Charles Chan7ffd81f2017-02-08 15:52:08 -0800472 // Warning log will come from getPortNextObjective method
Saurav Das59232cf2016-04-27 18:35:50 -0700473 return null;
474 }
Charles Chand2990362016-04-18 13:44:03 -0700475
476 return DefaultForwardingObjective.builder()
477 .withFlag(ForwardingObjective.Flag.SPECIFIC)
478 .withSelector(sbuilder.build())
479 .nextStep(portNextObjId)
480 .withPriority(100)
481 .fromApp(srManager.appId)
482 .makePermanent();
483 }
484
Charles Chan93e71ba2016-04-29 14:38:22 -0700485 /**
Charles Chanb7b4c932017-06-15 19:25:25 -0700486 * Populate or revoke a bridging rule on given deviceId that matches given mac, given vlan and
487 * output to given port.
488 *
489 * @param deviceId device ID
490 * @param port port
491 * @param mac mac address
492 * @param vlanId VLAN ID
493 * @param revoke true to revoke the rule; false to populate
494 */
495 private void processBridgingRule(DeviceId deviceId, PortNumber port, MacAddress mac,
496 VlanId vlanId, boolean revoke) {
497 log.debug("{} bridging entry for host {}/{} at {}:{}", revoke ? "Revoking" : "Populating",
498 mac, vlanId, deviceId, port);
499
500 ForwardingObjective.Builder fob = bridgingFwdObjBuilder(deviceId, mac, vlanId, port, revoke);
501 if (fob == null) {
502 log.warn("Fail to build fwd obj for host {}/{}. Abort.", mac, vlanId);
503 return;
504 }
505
506 ObjectiveContext context = new DefaultObjectiveContext(
507 (objective) -> log.debug("Brigding rule for {}/{} {}", mac, vlanId,
508 revoke ? "revoked" : "populated"),
509 (objective, error) -> log.warn("Failed to {} bridging rule for {}/{}: {}",
510 revoke ? "revoked" : "populated", mac, vlanId, error));
511 flowObjectiveService.forward(deviceId, revoke ? fob.remove(context) : fob.add(context));
512 }
513
514 /**
515 * Populate or revoke a routing rule on given deviceId that matches given ip,
516 * set destination mac to given mac, set vlan to given vlan and output to given port.
517 *
518 * @param deviceId device ID
519 * @param port port
520 * @param mac mac address
521 * @param vlanId VLAN ID
522 * @param ip IP address
523 * @param revoke true to revoke the rule; false to populate
524 */
525 private void processRoutingRule(DeviceId deviceId, PortNumber port, MacAddress mac,
526 VlanId vlanId, IpAddress ip, boolean revoke) {
527 ConnectPoint location = new ConnectPoint(deviceId, port);
Charles Chanf9a52702017-06-16 15:19:24 -0700528 if (!srManager.deviceConfiguration.inSameSubnet(location, ip)) {
529 log.info("{} is not included in the subnet config of {}/{}. Ignored.", ip, deviceId, port);
530 return;
Charles Chanb7b4c932017-06-15 19:25:25 -0700531 }
Charles Chanb7b4c932017-06-15 19:25:25 -0700532
Charles Chanf9a52702017-06-16 15:19:24 -0700533 log.info("{} routing rule for {} at {}", revoke ? "Revoking" : "Populating", ip, location);
534 if (revoke) {
Charles Chan23686832017-08-23 14:46:43 -0700535 srManager.defaultRoutingHandler.revokeRoute(deviceId, ip.toIpPrefix(), mac, vlanId, port);
Charles Chanf9a52702017-06-16 15:19:24 -0700536 } else {
Charles Chan23686832017-08-23 14:46:43 -0700537 srManager.defaultRoutingHandler.populateRoute(deviceId, ip.toIpPrefix(), mac, vlanId, port);
Charles Chan6ea94fc2016-05-10 17:29:47 -0700538 }
Charles Chan6ea94fc2016-05-10 17:29:47 -0700539 }
Jonghwan Hyun5f1def82017-08-25 17:48:36 -0700540
541 /**
542 * Populate or revoke a bridging rule on given deviceId that matches given vlanId,
543 * and hostMAC connected to given port, and output to given port only when
544 * vlan information is valid.
545 *
546 * @param deviceId device ID that host attaches to
547 * @param portNum port number that host attaches to
548 * @param hostMac mac address of the host connected to the switch port
549 * @param vlanId Vlan ID configured on the switch port
550 * @param popVlan true to pop Vlan tag at TrafficTreatment, false otherwise
551 * @param install true to populate the objective, false to revoke
552 */
553 private void updateBridgingRule(DeviceId deviceId, PortNumber portNum, MacAddress hostMac,
554 VlanId vlanId, boolean popVlan, boolean install) {
555 // Create host selector
556 TrafficSelector.Builder sbuilder = DefaultTrafficSelector.builder();
557 sbuilder.matchEthDst(hostMac);
558
559 // Create host meta
560 TrafficSelector.Builder mbuilder = DefaultTrafficSelector.builder();
561
562 sbuilder.matchVlanId(vlanId);
563 mbuilder.matchVlanId(vlanId);
564
565 // Create host treatment
566 TrafficTreatment.Builder tbuilder = DefaultTrafficTreatment.builder();
567 tbuilder.immediate().setOutput(portNum);
568
569 if (popVlan) {
570 tbuilder.immediate().popVlan();
571 }
572
573 int portNextObjId = srManager.getPortNextObjectiveId(deviceId, portNum,
574 tbuilder.build(), mbuilder.build(), install);
575 if (portNextObjId != -1) {
576 ForwardingObjective.Builder fob = DefaultForwardingObjective.builder()
577 .withFlag(ForwardingObjective.Flag.SPECIFIC)
578 .withSelector(sbuilder.build())
579 .nextStep(portNextObjId)
580 .withPriority(100)
581 .fromApp(srManager.appId)
582 .makePermanent();
583
584 ObjectiveContext context = new DefaultObjectiveContext(
585 (objective) -> log.debug("Brigding rule for {}/{} {}", hostMac, vlanId,
586 install ? "populated" : "revoked"),
587 (objective, error) -> log.warn("Failed to {} bridging rule for {}/{}: {}",
588 install ? "populate" : "revoke", hostMac, vlanId, error));
589 flowObjectiveService.forward(deviceId, install ? fob.add(context) : fob.remove(context));
590 } else {
591 log.warn("Failed to retrieve next objective for {}/{}", hostMac, vlanId);
592 }
593 }
594
595 /**
596 * Update forwarding objective for unicast bridging and unicast routing.
597 * Also check the validity of updated interface configuration on VLAN.
598 *
599 * @param deviceId device ID that host attaches to
600 * @param portNum port number that host attaches to
601 * @param vlanId Vlan ID configured on the switch port
602 * @param popVlan true to pop Vlan tag at TrafficTreatment, false otherwise
603 * @param install true to populate the objective, false to revoke
604 */
605 void processIntfVlanUpdatedEvent(DeviceId deviceId, PortNumber portNum, VlanId vlanId,
606 boolean popVlan, boolean install) {
607 ConnectPoint connectPoint = new ConnectPoint(deviceId, portNum);
608 Set<Host> hosts = hostService.getConnectedHosts(connectPoint);
609
610 if (hosts == null || hosts.size() == 0) {
611 return;
612 }
613
614 hosts.forEach(host -> {
615 MacAddress mac = host.mac();
616 VlanId hostVlanId = host.vlan();
617
618 // Check whether the host vlan is valid for new interface configuration
619 if ((!popVlan && hostVlanId.equals(vlanId)) ||
620 (popVlan && hostVlanId.equals(VlanId.NONE))) {
621 updateBridgingRule(deviceId, portNum, mac, vlanId, popVlan, install);
622 // Update Forwarding objective and corresponding simple Next objective
623 // for each host and IP address connected to given port
624 host.ipAddresses().forEach(ipAddress ->
625 srManager.routingRulePopulator.updateFwdObj(deviceId, portNum, ipAddress.toIpPrefix(),
626 mac, vlanId, popVlan, install)
627 );
628 }
629 });
630 }
631
632 /**
633 * Populate or revoke routing rule for each host, according to the updated
634 * subnet configuration on the interface.
635 * @param cp connect point of the updated interface
636 * @param ipPrefixSet IP Prefixes added or removed
637 * @param install true if IP Prefixes added, false otherwise
638 */
639 void processIntfIpUpdatedEvent(ConnectPoint cp, Set<IpPrefix> ipPrefixSet, boolean install) {
640 Set<Host> hosts = hostService.getConnectedHosts(cp);
641
642 if (hosts == null || hosts.size() == 0) {
643 log.warn("processIntfIpUpdatedEvent: No hosts connected to {}", cp);
644 return;
645 }
646
647 // Check whether the host IP address is in the interface's subnet
648 hosts.forEach(host ->
649 host.ipAddresses().forEach(hostIpAddress -> {
650 ipPrefixSet.forEach(ipPrefix -> {
651 if (install && ipPrefix.contains(hostIpAddress)) {
652 srManager.routingRulePopulator.populateRoute(cp.deviceId(), hostIpAddress.toIpPrefix(),
653 host.mac(), host.vlan(), cp.port());
654 } else if (!install && ipPrefix.contains(hostIpAddress)) {
655 srManager.routingRulePopulator.revokeRoute(cp.deviceId(), hostIpAddress.toIpPrefix(),
656 host.mac(), host.vlan(), cp.port());
657 }
658 });
659 }));
660 }
Saurav Dase9c8971e2018-01-18 12:07:33 -0800661
662 /**
663 * Returns the set of portnumbers on the given device that are part of the
664 * locations for dual-homed hosts.
665 *
666 * @param deviceId the given deviceId
667 * @return set of port numbers on given device that are dual-homed host
668 * locations. May be empty if no dual homed hosts are connected to
669 * the given device
670 */
671 Set<PortNumber> getDualHomedHostPorts(DeviceId deviceId) {
672 Set<PortNumber> dualHomedLocations = new HashSet<>();
673 srManager.hostService.getConnectedHosts(deviceId).stream()
674 .filter(host -> host.locations().size() == 2)
675 .forEach(host -> host.locations().stream()
676 .filter(loc -> loc.deviceId().equals(deviceId))
677 .forEach(loc -> dualHomedLocations.add(loc.port())));
678 return dualHomedLocations;
679 }
Charles Chand2990362016-04-18 13:44:03 -0700680}