9a8236304cf379a06a48dc1539672af0782ff6b7
[onosfw.git] /
1 /*
2  * Copyright 2014-2015 Open Networking Laboratory
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 package org.onosproject.provider.host.impl;
17
18 import org.apache.felix.scr.annotations.Activate;
19 import org.apache.felix.scr.annotations.Component;
20 import org.apache.felix.scr.annotations.Deactivate;
21 import org.apache.felix.scr.annotations.Modified;
22 import org.apache.felix.scr.annotations.Property;
23 import org.apache.felix.scr.annotations.Reference;
24 import org.apache.felix.scr.annotations.ReferenceCardinality;
25 import org.onlab.packet.ARP;
26 import org.onlab.packet.Ethernet;
27 import org.onlab.packet.ICMP6;
28 import org.onlab.packet.IPacket;
29 import org.onlab.packet.IPv6;
30 import org.onlab.packet.IpAddress;
31 import org.onlab.packet.MacAddress;
32 import org.onlab.packet.VlanId;
33 import org.onlab.packet.ipv6.IExtensionHeader;
34 import org.onlab.packet.ndp.NeighborAdvertisement;
35 import org.onlab.packet.ndp.NeighborSolicitation;
36 import org.onlab.packet.ndp.RouterAdvertisement;
37 import org.onlab.packet.ndp.RouterSolicitation;
38 import org.onosproject.cfg.ComponentConfigService;
39 import org.onosproject.core.ApplicationId;
40 import org.onosproject.core.CoreService;
41 import org.onosproject.net.ConnectPoint;
42 import org.onosproject.net.Device;
43 import org.onosproject.net.Host;
44 import org.onosproject.net.HostId;
45 import org.onosproject.net.HostLocation;
46 import org.onosproject.net.device.DeviceEvent;
47 import org.onosproject.net.device.DeviceListener;
48 import org.onosproject.net.device.DeviceService;
49 import org.onosproject.net.flow.DefaultTrafficSelector;
50 import org.onosproject.net.flow.TrafficSelector;
51 import org.onosproject.net.host.DefaultHostDescription;
52 import org.onosproject.net.host.HostDescription;
53 import org.onosproject.net.host.HostProvider;
54 import org.onosproject.net.host.HostProviderRegistry;
55 import org.onosproject.net.host.HostProviderService;
56 import org.onosproject.net.host.HostService;
57 import org.onosproject.net.packet.PacketContext;
58 import org.onosproject.net.packet.PacketPriority;
59 import org.onosproject.net.packet.PacketProcessor;
60 import org.onosproject.net.packet.PacketService;
61 import org.onosproject.net.provider.AbstractProvider;
62 import org.onosproject.net.provider.ProviderId;
63 import org.onosproject.net.topology.Topology;
64 import org.onosproject.net.topology.TopologyService;
65 import org.osgi.service.component.ComponentContext;
66 import org.slf4j.Logger;
67
68 import java.util.Dictionary;
69 import java.util.Set;
70
71 import static com.google.common.base.Strings.isNullOrEmpty;
72 import static org.slf4j.LoggerFactory.getLogger;
73
74 /**
75  * Provider which uses an OpenFlow controller to detect network end-station
76  * hosts.
77  */
78 @Component(immediate = true)
79 public class HostLocationProvider extends AbstractProvider implements HostProvider {
80
81     private final Logger log = getLogger(getClass());
82
83     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
84     protected CoreService coreService;
85
86     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
87     protected HostProviderRegistry providerRegistry;
88
89     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
90     protected PacketService packetService;
91
92     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
93     protected TopologyService topologyService;
94
95     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
96     protected HostService hostService;
97
98     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
99     protected DeviceService deviceService;
100
101     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
102     protected ComponentConfigService cfgService;
103
104     private HostProviderService providerService;
105
106     private final InternalHostProvider processor = new InternalHostProvider();
107     private final DeviceListener deviceListener = new InternalDeviceListener();
108
109     private ApplicationId appId;
110
111     @Property(name = "hostRemovalEnabled", boolValue = true,
112             label = "Enable host removal on port/device down events")
113     private boolean hostRemovalEnabled = true;
114
115     @Property(name = "ipv6NeighborDiscovery", boolValue = false,
116             label = "Enable using IPv6 Neighbor Discovery by the " +
117                     "Host Location Provider; default is false")
118     private boolean ipv6NeighborDiscovery = false;
119
120     /**
121      * Creates an OpenFlow host provider.
122      */
123     public HostLocationProvider() {
124         super(new ProviderId("of", "org.onosproject.provider.host"));
125     }
126
127     @Activate
128     public void activate(ComponentContext context) {
129         cfgService.registerProperties(getClass());
130         appId = coreService.registerApplication("org.onosproject.provider.host");
131
132         providerService = providerRegistry.register(this);
133         packetService.addProcessor(processor, PacketProcessor.advisor(1));
134         deviceService.addListener(deviceListener);
135         readComponentConfiguration(context);
136         requestIntercepts();
137
138         log.info("Started with Application ID {}", appId.id());
139     }
140
141     @Deactivate
142     public void deactivate() {
143         cfgService.unregisterProperties(getClass(), false);
144
145         withdrawIntercepts();
146
147         providerRegistry.unregister(this);
148         packetService.removeProcessor(processor);
149         deviceService.removeListener(deviceListener);
150         providerService = null;
151         log.info("Stopped");
152     }
153
154     @Modified
155     public void modified(ComponentContext context) {
156         readComponentConfiguration(context);
157         requestIntercepts();
158     }
159
160     /**
161      * Request packet intercepts.
162      */
163     private void requestIntercepts() {
164         TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
165         selector.matchEthType(Ethernet.TYPE_ARP);
166         packetService.requestPackets(selector.build(), PacketPriority.CONTROL, appId);
167
168         // IPv6 Neighbor Solicitation packet.
169         selector.matchEthType(Ethernet.TYPE_IPV6);
170         selector.matchIPProtocol(IPv6.PROTOCOL_ICMP6);
171         selector.matchIcmpv6Type(ICMP6.NEIGHBOR_SOLICITATION);
172         if (ipv6NeighborDiscovery) {
173             packetService.requestPackets(selector.build(), PacketPriority.CONTROL, appId);
174         } else {
175             packetService.cancelPackets(selector.build(), PacketPriority.CONTROL, appId);
176         }
177
178         // IPv6 Neighbor Advertisement packet.
179         selector.matchIcmpv6Type(ICMP6.NEIGHBOR_ADVERTISEMENT);
180         if (ipv6NeighborDiscovery) {
181             packetService.requestPackets(selector.build(), PacketPriority.CONTROL, appId);
182         } else {
183             packetService.cancelPackets(selector.build(), PacketPriority.CONTROL, appId);
184         }
185     }
186
187     /**
188      * Withdraw packet intercepts.
189      */
190     private void withdrawIntercepts() {
191         TrafficSelector.Builder selector = DefaultTrafficSelector.builder();
192         selector.matchEthType(Ethernet.TYPE_ARP);
193         packetService.cancelPackets(selector.build(), PacketPriority.CONTROL, appId);
194
195         // IPv6 Neighbor Solicitation packet.
196         selector.matchEthType(Ethernet.TYPE_IPV6);
197         selector.matchIPProtocol(IPv6.PROTOCOL_ICMP6);
198         selector.matchIcmpv6Type(ICMP6.NEIGHBOR_SOLICITATION);
199         packetService.cancelPackets(selector.build(), PacketPriority.CONTROL, appId);
200
201         // IPv6 Neighbor Advertisement packet.
202         selector.matchIcmpv6Type(ICMP6.NEIGHBOR_ADVERTISEMENT);
203         packetService.cancelPackets(selector.build(), PacketPriority.CONTROL, appId);
204     }
205
206     /**
207      * Extracts properties from the component configuration context.
208      *
209      * @param context the component context
210      */
211     private void readComponentConfiguration(ComponentContext context) {
212         Dictionary<?, ?> properties = context.getProperties();
213         Boolean flag;
214
215         flag = isPropertyEnabled(properties, "hostRemovalEnabled");
216         if (flag == null) {
217             log.info("Host removal on port/device down events is not configured, " +
218                              "using current value of {}", hostRemovalEnabled);
219         } else {
220             hostRemovalEnabled = flag;
221             log.info("Configured. Host removal on port/device down events is {}",
222                      hostRemovalEnabled ? "enabled" : "disabled");
223         }
224
225         flag = isPropertyEnabled(properties, "ipv6NeighborDiscovery");
226         if (flag == null) {
227             log.info("Using IPv6 Neighbor Discovery is not configured, " +
228                              "using current value of {}", ipv6NeighborDiscovery);
229         } else {
230             ipv6NeighborDiscovery = flag;
231             log.info("Configured. Using IPv6 Neighbor Discovery is {}",
232                      ipv6NeighborDiscovery ? "enabled" : "disabled");
233         }
234     }
235
236     /**
237      * Check property name is defined and set to true.
238      *
239      * @param properties   properties to be looked up
240      * @param propertyName the name of the property to look up
241      * @return value when the propertyName is defined or return null
242      */
243     private static Boolean isPropertyEnabled(Dictionary<?, ?> properties,
244                                              String propertyName) {
245         Boolean value = null;
246         try {
247             String s = (String) properties.get(propertyName);
248             value = isNullOrEmpty(s) ? null : s.trim().equals("true");
249         } catch (ClassCastException e) {
250             // No propertyName defined.
251             value = null;
252         }
253         return value;
254     }
255
256     @Override
257     public void triggerProbe(Host host) {
258         log.info("Triggering probe on device {}", host);
259     }
260
261     private class InternalHostProvider implements PacketProcessor {
262         /**
263          * Update host location only.
264          *
265          * @param hid  host ID
266          * @param mac  source Mac address
267          * @param vlan VLAN ID
268          * @param hloc host location
269          */
270         private void updateLocation(HostId hid, MacAddress mac,
271                                     VlanId vlan, HostLocation hloc) {
272             HostDescription desc = new DefaultHostDescription(mac, vlan, hloc);
273             try {
274                 providerService.hostDetected(hid, desc);
275             } catch (IllegalStateException e) {
276                 log.debug("Host {} suppressed", hid);
277             }
278         }
279
280         /**
281          * Update host location and IP address.
282          *
283          * @param hid  host ID
284          * @param mac  source Mac address
285          * @param vlan VLAN ID
286          * @param hloc host location
287          * @param ip   source IP address
288          */
289         private void updateLocationIP(HostId hid, MacAddress mac,
290                                       VlanId vlan, HostLocation hloc,
291                                       IpAddress ip) {
292             HostDescription desc = ip.isZero() || ip.isSelfAssigned() ?
293                     new DefaultHostDescription(mac, vlan, hloc) :
294                     new DefaultHostDescription(mac, vlan, hloc, ip);
295             try {
296                 providerService.hostDetected(hid, desc);
297             } catch (IllegalStateException e) {
298                 log.debug("Host {} suppressed", hid);
299             }
300         }
301
302         @Override
303         public void process(PacketContext context) {
304             if (context == null) {
305                 return;
306             }
307
308             Ethernet eth = context.inPacket().parsed();
309             if (eth == null) {
310                 return;
311             }
312             MacAddress srcMac = eth.getSourceMAC();
313
314             VlanId vlan = VlanId.vlanId(eth.getVlanID());
315             ConnectPoint heardOn = context.inPacket().receivedFrom();
316
317             // If this arrived on control port, bail out.
318             if (heardOn.port().isLogical()) {
319                 return;
320             }
321
322             // If this is not an edge port, bail out.
323             Topology topology = topologyService.currentTopology();
324             if (topologyService.isInfrastructure(topology, heardOn)) {
325                 return;
326             }
327
328             HostLocation hloc = new HostLocation(heardOn, System.currentTimeMillis());
329             HostId hid = HostId.hostId(eth.getSourceMAC(), vlan);
330
331             // ARP: possible new hosts, update both location and IP
332             if (eth.getEtherType() == Ethernet.TYPE_ARP) {
333                 ARP arp = (ARP) eth.getPayload();
334                 IpAddress ip = IpAddress.valueOf(IpAddress.Version.INET,
335                                                  arp.getSenderProtocolAddress());
336                 updateLocationIP(hid, srcMac, vlan, hloc, ip);
337
338                 // IPv4: update location only
339             } else if (eth.getEtherType() == Ethernet.TYPE_IPV4) {
340                 updateLocation(hid, srcMac, vlan, hloc);
341
342                 //
343                 // NeighborAdvertisement and NeighborSolicitation: possible
344                 // new hosts, update both location and IP.
345                 //
346                 // IPv6: update location only
347             } else if (eth.getEtherType() == Ethernet.TYPE_IPV6) {
348                 IPv6 ipv6 = (IPv6) eth.getPayload();
349                 IpAddress ip = IpAddress.valueOf(IpAddress.Version.INET6,
350                                                  ipv6.getSourceAddress());
351
352                 // skip extension headers
353                 IPacket pkt = ipv6;
354                 while (pkt.getPayload() != null &&
355                         pkt.getPayload() instanceof IExtensionHeader) {
356                     pkt = pkt.getPayload();
357                 }
358
359                 // Neighbor Discovery Protocol
360                 pkt = pkt.getPayload();
361                 if (pkt != null && pkt instanceof ICMP6) {
362                     pkt = pkt.getPayload();
363                     // RouterSolicitation, RouterAdvertisement
364                     if (pkt != null && (pkt instanceof RouterAdvertisement ||
365                             pkt instanceof RouterSolicitation)) {
366                         return;
367                     }
368                     if (pkt != null && (pkt instanceof NeighborSolicitation ||
369                             pkt instanceof NeighborAdvertisement)) {
370                         // Duplicate Address Detection
371                         if (ip.isZero()) {
372                             return;
373                         }
374                         // NeighborSolicitation, NeighborAdvertisement
375                         updateLocationIP(hid, srcMac, vlan, hloc, ip);
376                         return;
377                     }
378                 }
379
380                 // multicast
381                 if (eth.isMulticast()) {
382                     return;
383                 }
384
385                 // normal IPv6 packets
386                 updateLocation(hid, srcMac, vlan, hloc);
387             }
388         }
389     }
390
391     // Auxiliary listener to device events.
392     private class InternalDeviceListener implements DeviceListener {
393         @Override
394         public void event(DeviceEvent event) {
395             Device device = event.subject();
396             switch (event.type()) {
397                 case DEVICE_ADDED:
398                     break;
399                 case DEVICE_AVAILABILITY_CHANGED:
400                     if (hostRemovalEnabled &&
401                             !deviceService.isAvailable(device.id())) {
402                         removeHosts(hostService.getConnectedHosts(device.id()));
403                     }
404                     break;
405                 case DEVICE_SUSPENDED:
406                 case DEVICE_UPDATED:
407                     // Nothing to do?
408                     break;
409                 case DEVICE_REMOVED:
410                     if (hostRemovalEnabled) {
411                         removeHosts(hostService.getConnectedHosts(device.id()));
412                     }
413                     break;
414                 case PORT_ADDED:
415                     break;
416                 case PORT_UPDATED:
417                     if (hostRemovalEnabled) {
418                         ConnectPoint point =
419                                 new ConnectPoint(device.id(), event.port().number());
420                         removeHosts(hostService.getConnectedHosts(point));
421                     }
422                     break;
423                 case PORT_REMOVED:
424                     // Nothing to do?
425                     break;
426                 default:
427                     break;
428             }
429         }
430     }
431
432     // Signals host vanish for all specified hosts.
433     private void removeHosts(Set<Host> hosts) {
434         for (Host host : hosts) {
435             providerService.hostVanished(host.id());
436         }
437     }
438
439 }