blob: 0ca1ba90e1294341fc7af5e3e6744f1ae8f53433 [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;
20import org.onlab.packet.MacAddress;
21import org.onlab.packet.VlanId;
Charles Chan59cc16d2017-02-02 16:20:42 -080022import org.onosproject.net.ConnectPoint;
Charles Chand2990362016-04-18 13:44:03 -070023import org.onosproject.net.DeviceId;
Charles Chan6ea94fc2016-05-10 17:29:47 -070024import org.onosproject.net.Host;
Charles Chan93e71ba2016-04-29 14:38:22 -070025import org.onosproject.net.HostLocation;
Charles Chand2990362016-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 Das018605f2017-02-18 14:05:44 -080041import com.google.common.collect.Sets;
Charles Chand2990362016-04-18 13:44:03 -070042import java.util.Set;
Charles Chanf9a52702017-06-16 15:19:24 -070043import java.util.stream.Collectors;
44
45import static com.google.common.base.Preconditions.checkArgument;
Charles Chand2990362016-04-18 13:44:03 -070046
47/**
48 * Handles host-related events.
49 */
50public class HostHandler {
51 private static final Logger log = LoggerFactory.getLogger(HostHandler.class);
Charles Chan2e2e3402017-06-19 14:00:53 -070052 protected final SegmentRoutingManager srManager;
Charles Chand2990362016-04-18 13:44:03 -070053 private HostService hostService;
54 private FlowObjectiveService flowObjectiveService;
55
56 /**
57 * Constructs the HostHandler.
58 *
59 * @param srManager Segment Routing manager
60 */
Charles Chanf9a52702017-06-16 15:19:24 -070061 HostHandler(SegmentRoutingManager srManager) {
Charles Chand2990362016-04-18 13:44:03 -070062 this.srManager = srManager;
Charles Chand2990362016-04-18 13:44:03 -070063 hostService = srManager.hostService;
64 flowObjectiveService = srManager.flowObjectiveService;
65 }
66
Charles Chan03a73e02016-10-24 14:52:01 -070067 protected void init(DeviceId devId) {
Charles Chanf9a52702017-06-16 15:19:24 -070068 hostService.getHosts().forEach(host ->
69 host.locations().stream()
70 .filter(location -> location.deviceId().equals(devId))
71 .forEach(location -> processHostAddedAtLocation(host, location))
72 );
Charles Chan93e71ba2016-04-29 14:38:22 -070073 }
Charles Chand2990362016-04-18 13:44:03 -070074
Charles Chanf9a52702017-06-16 15:19:24 -070075 void processHostAddedEvent(HostEvent event) {
Charles Chan35fd1a72016-06-13 18:54:31 -070076 processHostAdded(event.subject());
Charles Chan93e71ba2016-04-29 14:38:22 -070077 }
78
Charles Chanf9a52702017-06-16 15:19:24 -070079 private void processHostAdded(Host host) {
80 host.locations().forEach(location -> processHostAddedAtLocation(host, location));
Charles Chand2990362016-04-18 13:44:03 -070081 }
82
Charles Chanf9a52702017-06-16 15:19:24 -070083 void processHostAddedAtLocation(Host host, HostLocation location) {
84 checkArgument(host.locations().contains(location), "{} is not a location of {}", location, host);
85
86 MacAddress mac = host.mac();
87 VlanId vlanId = host.vlan();
88 Set<HostLocation> locations = host.locations();
89 Set<IpAddress> ips = host.ipAddresses();
90 log.info("Host {}/{} is added at {}", mac, vlanId, locations);
91
92 processBridgingRule(location.deviceId(), location.port(), mac, vlanId, false);
93 ips.forEach(ip ->
94 processRoutingRule(location.deviceId(), location.port(), mac, vlanId, ip, false)
95 );
96 }
97
98 void processHostRemovedEvent(HostEvent event) {
Charles Chan35fd1a72016-06-13 18:54:31 -070099 processHostRemoved(event.subject());
100 }
101
Charles Chanf9a52702017-06-16 15:19:24 -0700102 private void processHostRemoved(Host host) {
Charles Chan35fd1a72016-06-13 18:54:31 -0700103 MacAddress mac = host.mac();
104 VlanId vlanId = host.vlan();
Charles Chanf9a52702017-06-16 15:19:24 -0700105 Set<HostLocation> locations = host.locations();
Charles Chan35fd1a72016-06-13 18:54:31 -0700106 Set<IpAddress> ips = host.ipAddresses();
Charles Chanf9a52702017-06-16 15:19:24 -0700107 log.info("Host {}/{} is removed from {}", mac, vlanId, locations);
Charles Chan93e71ba2016-04-29 14:38:22 -0700108
Charles Chanf9a52702017-06-16 15:19:24 -0700109 locations.forEach(location -> {
110 processBridgingRule(location.deviceId(), location.port(), mac, vlanId, true);
111 ips.forEach(ip ->
112 processRoutingRule(location.deviceId(), location.port(), mac, vlanId, ip, true)
113 );
114 });
Charles Chan93e71ba2016-04-29 14:38:22 -0700115 }
116
Charles Chanf9a52702017-06-16 15:19:24 -0700117 void processHostMovedEvent(HostEvent event) {
Charles Chan93e71ba2016-04-29 14:38:22 -0700118 MacAddress mac = event.subject().mac();
119 VlanId vlanId = event.subject().vlan();
Charles Chanf9a52702017-06-16 15:19:24 -0700120 Set<HostLocation> prevLocations = event.prevSubject().locations();
Charles Chan93e71ba2016-04-29 14:38:22 -0700121 Set<IpAddress> prevIps = event.prevSubject().ipAddresses();
Charles Chanf9a52702017-06-16 15:19:24 -0700122 Set<HostLocation> newLocations = event.subject().locations();
Charles Chan93e71ba2016-04-29 14:38:22 -0700123 Set<IpAddress> newIps = event.subject().ipAddresses();
Charles Chanf9a52702017-06-16 15:19:24 -0700124 log.info("Host {}/{} is moved from {} to {}", mac, vlanId, prevLocations, newLocations);
Charles Chan93e71ba2016-04-29 14:38:22 -0700125
Charles Chanf9a52702017-06-16 15:19:24 -0700126 Set<DeviceId> newDeviceIds = newLocations.stream().map(HostLocation::deviceId)
127 .collect(Collectors.toSet());
Charles Chan93e71ba2016-04-29 14:38:22 -0700128
Charles Chanf9a52702017-06-16 15:19:24 -0700129 // For each old location
130 Sets.difference(prevLocations, newLocations).forEach(prevLocation -> {
131 // TODO Switch to backup link when pair device is configured
132
133 // Remove bridging rule and routing rules for unchanged IPs if the host moves from a switch to another.
134 // Otherwise, do not remove and let the adding part update the old flow
135 if (!newDeviceIds.contains(prevLocation.deviceId())) {
136 processBridgingRule(prevLocation.deviceId(), prevLocation.port(), mac, vlanId, true);
137 Sets.intersection(prevIps, newIps).forEach(ip ->
138 processRoutingRule(prevLocation.deviceId(), prevLocation.port(), mac, vlanId,
139 ip, true)
140 );
141 }
142
143 // Remove bridging rules if new interface vlan is different from old interface vlan
144 // Otherwise, do not remove and let the adding part update the old flow
145 if (newLocations.stream().noneMatch(newLocation -> {
146 VlanId oldAssignedVlan = srManager.getInternalVlanId(prevLocation);
147 VlanId newAssignedVlan = srManager.getInternalVlanId(newLocation);
148 // Host is tagged and the new location has the host vlan in vlan-tagged
149 return srManager.getTaggedVlanId(newLocation).contains(vlanId) ||
150 (oldAssignedVlan != null && newAssignedVlan != null &&
151 // Host is untagged and the new location has the same assigned vlan
152 oldAssignedVlan.equals(newAssignedVlan));
153 })) {
154 processBridgingRule(prevLocation.deviceId(), prevLocation.port(), mac, vlanId, true);
155 }
156
157 // Remove routing rules for unchanged IPs if none of the subnet of new location contains
158 // the IP. Otherwise, do not remove and let the adding part update the old flow
159 Sets.intersection(prevIps, newIps).forEach(ip -> {
160 if (newLocations.stream().noneMatch(newLocation ->
161 srManager.deviceConfiguration.inSameSubnet(newLocation, ip))) {
162 processRoutingRule(prevLocation.deviceId(), prevLocation.port(), mac, vlanId,
163 ip, true);
164 }
Charles Chan93e71ba2016-04-29 14:38:22 -0700165 });
Charles Chanf9a52702017-06-16 15:19:24 -0700166
167 // Remove routing rules for old IPs
168 Sets.difference(prevIps, newIps).forEach(ip ->
169 processRoutingRule(prevLocation.deviceId(), prevLocation.port(), mac, vlanId,
170 ip, true)
171 );
172 });
173
174 // For each new location, add all new IPs.
175 Sets.difference(newLocations, prevLocations).forEach(newLocation -> {
176 processBridgingRule(newLocation.deviceId(), newLocation.port(), mac, vlanId, false);
177 newIps.forEach(ip ->
178 processRoutingRule(newLocation.deviceId(), newLocation.port(), mac, vlanId,
179 ip, false)
180 );
181 });
182
183 // For each unchanged location, add new IPs and remove old IPs.
184 Sets.intersection(newLocations, prevLocations).forEach(unchangedLocation -> {
185 Sets.difference(prevIps, newIps).forEach(ip ->
186 processRoutingRule(unchangedLocation.deviceId(), unchangedLocation.port(), mac,
187 vlanId, ip, true)
188 );
189
190 Sets.difference(newIps, prevIps).forEach(ip ->
191 processRoutingRule(unchangedLocation.deviceId(), unchangedLocation.port(), mac,
192 vlanId, ip, false)
193 );
194 });
Charles Chan93e71ba2016-04-29 14:38:22 -0700195 }
196
Charles Chanf9a52702017-06-16 15:19:24 -0700197 void processHostUpdatedEvent(HostEvent event) {
Charles Chan93e71ba2016-04-29 14:38:22 -0700198 MacAddress mac = event.subject().mac();
199 VlanId vlanId = event.subject().vlan();
Charles Chanf9a52702017-06-16 15:19:24 -0700200 Set<HostLocation> locations = event.subject().locations();
Charles Chan93e71ba2016-04-29 14:38:22 -0700201 Set<IpAddress> prevIps = event.prevSubject().ipAddresses();
Charles Chan93e71ba2016-04-29 14:38:22 -0700202 Set<IpAddress> newIps = event.subject().ipAddresses();
Saurav Das018605f2017-02-18 14:05:44 -0800203 log.info("Host {}/{} is updated", mac, vlanId);
Charles Chan93e71ba2016-04-29 14:38:22 -0700204
Charles Chanf9a52702017-06-16 15:19:24 -0700205 locations.forEach(location -> {
206 Sets.difference(prevIps, newIps).forEach(ip ->
207 processRoutingRule(location.deviceId(), location.port(), mac, vlanId, ip, true)
208 );
209 Sets.difference(newIps, prevIps).forEach(ip ->
210 processRoutingRule(location.deviceId(), location.port(), mac, vlanId, ip, false)
211 );
212 });
Charles Chan93e71ba2016-04-29 14:38:22 -0700213 }
214
215 /**
Charles Chanf6ec1532017-02-08 16:10:40 -0800216 * Generates a forwarding objective builder for bridging rules.
217 * <p>
218 * The forwarding objective bridges packets destined to a given MAC to
219 * given port on given device.
Charles Chan93e71ba2016-04-29 14:38:22 -0700220 *
221 * @param deviceId Device that host attaches to
222 * @param mac MAC address of the host
Charles Chan7ffd81f2017-02-08 15:52:08 -0800223 * @param hostVlanId VLAN ID of the host
Charles Chan93e71ba2016-04-29 14:38:22 -0700224 * @param outport Port that host attaches to
Saurav Das961beb22017-03-29 19:09:17 -0700225 * @param revoke true if forwarding objective is meant to revoke forwarding rule
Charles Chan93e71ba2016-04-29 14:38:22 -0700226 * @return Forwarding objective builder
227 */
Charles Chanf9a52702017-06-16 15:19:24 -0700228 ForwardingObjective.Builder bridgingFwdObjBuilder(
Charles Chan7ffd81f2017-02-08 15:52:08 -0800229 DeviceId deviceId, MacAddress mac, VlanId hostVlanId,
Saurav Das961beb22017-03-29 19:09:17 -0700230 PortNumber outport, boolean revoke) {
Charles Chan7ffd81f2017-02-08 15:52:08 -0800231 ConnectPoint connectPoint = new ConnectPoint(deviceId, outport);
232 VlanId untaggedVlan = srManager.getUntaggedVlanId(connectPoint);
233 Set<VlanId> taggedVlans = srManager.getTaggedVlanId(connectPoint);
234 VlanId nativeVlan = srManager.getNativeVlanId(connectPoint);
Charles Chand2990362016-04-18 13:44:03 -0700235
Charles Chan7ffd81f2017-02-08 15:52:08 -0800236 // Create host selector
Charles Chand2990362016-04-18 13:44:03 -0700237 TrafficSelector.Builder sbuilder = DefaultTrafficSelector.builder();
238 sbuilder.matchEthDst(mac);
Charles Chand2990362016-04-18 13:44:03 -0700239
Charles Chan7ffd81f2017-02-08 15:52:08 -0800240 // Create host treatment
Charles Chand2990362016-04-18 13:44:03 -0700241 TrafficTreatment.Builder tbuilder = DefaultTrafficTreatment.builder();
Charles Chand2990362016-04-18 13:44:03 -0700242 tbuilder.immediate().setOutput(outport);
243
Charles Chan7ffd81f2017-02-08 15:52:08 -0800244 // Create host meta
245 TrafficSelector.Builder mbuilder = DefaultTrafficSelector.builder();
246
247 // Adjust the selector, treatment and meta according to VLAN configuration
248 if (taggedVlans.contains(hostVlanId)) {
249 sbuilder.matchVlanId(hostVlanId);
250 mbuilder.matchVlanId(hostVlanId);
251 } else if (hostVlanId.equals(VlanId.NONE)) {
252 if (untaggedVlan != null) {
253 sbuilder.matchVlanId(untaggedVlan);
254 mbuilder.matchVlanId(untaggedVlan);
255 tbuilder.immediate().popVlan();
256 } else if (nativeVlan != null) {
257 sbuilder.matchVlanId(nativeVlan);
258 mbuilder.matchVlanId(nativeVlan);
259 tbuilder.immediate().popVlan();
260 } else {
Charles Chan3ca0b2e2017-05-26 14:23:58 -0700261 log.warn("Untagged host {}/{} is not allowed on {} without untagged or native" +
262 "vlan config", mac, hostVlanId, connectPoint);
263 return null;
Charles Chan7ffd81f2017-02-08 15:52:08 -0800264 }
265 } else {
266 log.warn("Tagged host {}/{} is not allowed on {} without VLAN listed in tagged vlan",
267 mac, hostVlanId, connectPoint);
268 return null;
269 }
Charles Chand2990362016-04-18 13:44:03 -0700270
271 // All forwarding is via Groups. Drivers can re-purpose to flow-actions if needed.
Saurav Das961beb22017-03-29 19:09:17 -0700272 // If the objective is to revoke an existing rule, and for some reason
273 // the next-objective does not exist, then a new one should not be created
Charles Chand2990362016-04-18 13:44:03 -0700274 int portNextObjId = srManager.getPortNextObjectiveId(deviceId, outport,
Saurav Das961beb22017-03-29 19:09:17 -0700275 tbuilder.build(), mbuilder.build(), !revoke);
Saurav Das59232cf2016-04-27 18:35:50 -0700276 if (portNextObjId == -1) {
Charles Chan7ffd81f2017-02-08 15:52:08 -0800277 // Warning log will come from getPortNextObjective method
Saurav Das59232cf2016-04-27 18:35:50 -0700278 return null;
279 }
Charles Chand2990362016-04-18 13:44:03 -0700280
281 return DefaultForwardingObjective.builder()
282 .withFlag(ForwardingObjective.Flag.SPECIFIC)
283 .withSelector(sbuilder.build())
284 .nextStep(portNextObjId)
285 .withPriority(100)
286 .fromApp(srManager.appId)
287 .makePermanent();
288 }
289
Charles Chan93e71ba2016-04-29 14:38:22 -0700290 /**
Charles Chanb7b4c932017-06-15 19:25:25 -0700291 * Populate or revoke a bridging rule on given deviceId that matches given mac, given vlan and
292 * output to given port.
293 *
294 * @param deviceId device ID
295 * @param port port
296 * @param mac mac address
297 * @param vlanId VLAN ID
298 * @param revoke true to revoke the rule; false to populate
299 */
300 private void processBridgingRule(DeviceId deviceId, PortNumber port, MacAddress mac,
301 VlanId vlanId, boolean revoke) {
302 log.debug("{} bridging entry for host {}/{} at {}:{}", revoke ? "Revoking" : "Populating",
303 mac, vlanId, deviceId, port);
304
305 ForwardingObjective.Builder fob = bridgingFwdObjBuilder(deviceId, mac, vlanId, port, revoke);
306 if (fob == null) {
307 log.warn("Fail to build fwd obj for host {}/{}. Abort.", mac, vlanId);
308 return;
309 }
310
311 ObjectiveContext context = new DefaultObjectiveContext(
312 (objective) -> log.debug("Brigding rule for {}/{} {}", mac, vlanId,
313 revoke ? "revoked" : "populated"),
314 (objective, error) -> log.warn("Failed to {} bridging rule for {}/{}: {}",
315 revoke ? "revoked" : "populated", mac, vlanId, error));
316 flowObjectiveService.forward(deviceId, revoke ? fob.remove(context) : fob.add(context));
317 }
318
319 /**
320 * Populate or revoke a routing rule on given deviceId that matches given ip,
321 * set destination mac to given mac, set vlan to given vlan and output to given port.
322 *
323 * @param deviceId device ID
324 * @param port port
325 * @param mac mac address
326 * @param vlanId VLAN ID
327 * @param ip IP address
328 * @param revoke true to revoke the rule; false to populate
329 */
330 private void processRoutingRule(DeviceId deviceId, PortNumber port, MacAddress mac,
331 VlanId vlanId, IpAddress ip, boolean revoke) {
332 ConnectPoint location = new ConnectPoint(deviceId, port);
Charles Chanf9a52702017-06-16 15:19:24 -0700333 if (!srManager.deviceConfiguration.inSameSubnet(location, ip)) {
334 log.info("{} is not included in the subnet config of {}/{}. Ignored.", ip, deviceId, port);
335 return;
Charles Chanb7b4c932017-06-15 19:25:25 -0700336 }
Charles Chanb7b4c932017-06-15 19:25:25 -0700337
Charles Chanf9a52702017-06-16 15:19:24 -0700338 log.info("{} routing rule for {} at {}", revoke ? "Revoking" : "Populating", ip, location);
339 if (revoke) {
340 srManager.routingRulePopulator.revokeRoute(deviceId, ip.toIpPrefix(), mac, vlanId, port);
341 } else {
342 srManager.routingRulePopulator.populateRoute(deviceId, ip.toIpPrefix(), mac, vlanId, port);
Charles Chan6ea94fc2016-05-10 17:29:47 -0700343 }
Charles Chan6ea94fc2016-05-10 17:29:47 -0700344 }
Charles Chand2990362016-04-18 13:44:03 -0700345}