blob: 7356d95ddfc7541254518d7c9aa93897b3d4f91b [file] [log] [blame]
Jonathan Hart63eeac32016-06-20 15:55:16 -07001/*
Jonathan Hartf4bd0482017-01-27 15:11:18 -08002 * Copyright 2017-present Open Networking Laboratory
Jonathan Hart63eeac32016-06-20 15:55:16 -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.routing.impl;
18
19import com.google.common.cache.Cache;
20import com.google.common.cache.CacheBuilder;
21import org.apache.felix.scr.annotations.Activate;
22import org.apache.felix.scr.annotations.Component;
23import org.apache.felix.scr.annotations.Deactivate;
24import org.apache.felix.scr.annotations.Modified;
25import org.apache.felix.scr.annotations.Property;
26import org.apache.felix.scr.annotations.Reference;
27import org.apache.felix.scr.annotations.ReferenceCardinality;
28import org.onlab.packet.EthType;
29import org.onlab.packet.Ethernet;
30import org.onlab.packet.IPv4;
Vinayak Tejankar3a409c62017-01-12 02:20:53 +053031import org.onlab.packet.IPv6;
32import org.onlab.packet.IP;
Jonathan Hart63eeac32016-06-20 15:55:16 -070033import org.onlab.packet.IpAddress;
34import org.onlab.packet.MacAddress;
35import org.onlab.packet.VlanId;
36import org.onlab.util.Tools;
37import org.onosproject.cfg.ComponentConfigService;
38import org.onosproject.core.ApplicationId;
39import org.onosproject.core.CoreService;
40import org.onosproject.incubator.net.intf.Interface;
41import org.onosproject.incubator.net.intf.InterfaceService;
42import org.onosproject.net.ConnectPoint;
43import org.onosproject.net.Host;
44import org.onosproject.net.flow.DefaultTrafficSelector;
45import org.onosproject.net.flow.DefaultTrafficTreatment;
46import org.onosproject.net.flow.TrafficSelector;
47import org.onosproject.net.host.HostEvent;
48import org.onosproject.net.host.HostListener;
49import org.onosproject.net.host.HostService;
50import org.onosproject.net.packet.DefaultOutboundPacket;
51import org.onosproject.net.packet.OutboundPacket;
52import org.onosproject.net.packet.PacketContext;
53import org.onosproject.net.packet.PacketPriority;
54import org.onosproject.net.packet.PacketProcessor;
55import org.onosproject.net.packet.PacketService;
56import org.osgi.service.component.ComponentContext;
57import org.slf4j.Logger;
58import org.slf4j.LoggerFactory;
59
60import java.nio.ByteBuffer;
61import java.util.Optional;
62import java.util.Queue;
63import java.util.concurrent.ConcurrentLinkedQueue;
64import java.util.concurrent.TimeUnit;
65
66import static com.google.common.base.Preconditions.checkNotNull;
Vinayak Tejankar3a409c62017-01-12 02:20:53 +053067import static org.onlab.packet.IpAddress.Version.INET6;
Jonathan Hart63eeac32016-06-20 15:55:16 -070068
69/**
70 * Reactively handles sending packets to hosts that are directly connected to
71 * router interfaces.
72 */
73@Component(immediate = true, enabled = false)
74public class DirectHostManager {
75
76 private Logger log = LoggerFactory.getLogger(getClass());
77
78 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
79 protected PacketService packetService;
80
81 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
82 protected InterfaceService interfaceService;
83
84 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
85 protected HostService hostService;
86
87 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
88 protected CoreService coreService;
89
90 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
91 protected ComponentConfigService componentConfigService;
92
93 private static final boolean DEFAULT_ENABLED = false;
94
95 @Property(name = "enabled", boolValue = DEFAULT_ENABLED,
96 label = "Enable reactive directly-connected host processing")
97 private volatile boolean enabled = DEFAULT_ENABLED;
98
99 private static final String APP_NAME = "org.onosproject.directhost";
100
101 private static final long MAX_QUEUED_PACKETS = 10000;
102 private static final long MAX_QUEUE_DURATION = 2; // seconds
103
104 private ApplicationId appId;
105
106 private InternalPacketProcessor packetProcessor = new InternalPacketProcessor();
107 private InternalHostListener hostListener = new InternalHostListener();
108
Vinayak Tejankar3a409c62017-01-12 02:20:53 +0530109 private Cache<IpAddress, Queue<IP>> ipPacketCache = CacheBuilder.newBuilder()
110 .weigher((IpAddress key, Queue<IP> value) -> value.size())
Jonathan Hart63eeac32016-06-20 15:55:16 -0700111 .maximumWeight(MAX_QUEUED_PACKETS)
112 .expireAfterAccess(MAX_QUEUE_DURATION, TimeUnit.SECONDS)
113 .build();
114
115 @Activate
116 public void activate(ComponentContext context) {
117 componentConfigService.registerProperties(getClass());
Jonathan Hart63eeac32016-06-20 15:55:16 -0700118 appId = coreService.registerApplication(APP_NAME);
Charles Chan00d8b5f2016-12-04 17:17:39 -0800119 modified(context);
Jonathan Hart63eeac32016-06-20 15:55:16 -0700120 }
121
122 @Modified
123 private void modified(ComponentContext context) {
124 Boolean boolEnabled = Tools.isPropertyEnabled(context.getProperties(), "enabled");
125 if (boolEnabled != null) {
126 if (enabled && !boolEnabled) {
127 enabled = false;
128 disable();
129 } else if (!enabled && boolEnabled) {
130 enabled = true;
131 enable();
132 }
133 }
134 }
135
136 private void enable() {
137 hostService.addListener(hostListener);
138 packetService.addProcessor(packetProcessor, PacketProcessor.director(3));
Vinayak Tejankar3a409c62017-01-12 02:20:53 +0530139 // Requests packets for IPv4 traffic.
Jonathan Hart63eeac32016-06-20 15:55:16 -0700140 TrafficSelector selector = DefaultTrafficSelector.builder()
141 .matchEthType(EthType.EtherType.IPV4.ethType().toShort()).build();
142 packetService.requestPackets(selector, PacketPriority.REACTIVE, appId, Optional.empty());
Vinayak Tejankar3a409c62017-01-12 02:20:53 +0530143 // Requests packets for IPv6 traffic.
144 selector = DefaultTrafficSelector.builder()
145 .matchEthType(EthType.EtherType.IPV6.ethType().toShort()).build();
146 packetService.requestPackets(selector, PacketPriority.REACTIVE, appId, Optional.empty());
Jonathan Hart63eeac32016-06-20 15:55:16 -0700147 }
148
149 private void disable() {
150 packetService.removeProcessor(packetProcessor);
151 hostService.removeListener(hostListener);
Vinayak Tejankar3a409c62017-01-12 02:20:53 +0530152 // Withdraws IPv4 request.
Jonathan Hart63eeac32016-06-20 15:55:16 -0700153 TrafficSelector selector = DefaultTrafficSelector.builder()
154 .matchEthType(EthType.EtherType.IPV4.ethType().toShort()).build();
155 packetService.cancelPackets(selector, PacketPriority.REACTIVE, appId, Optional.empty());
Vinayak Tejankar3a409c62017-01-12 02:20:53 +0530156 // Withdraws IPv6 request.
157 selector = DefaultTrafficSelector.builder()
158 .matchEthType(EthType.EtherType.IPV6.ethType().toShort()).build();
159 packetService.cancelPackets(selector, PacketPriority.REACTIVE, appId, Optional.empty());
Jonathan Hart63eeac32016-06-20 15:55:16 -0700160 }
161
162 @Deactivate
163 public void deactivate() {
Charles Chan00d8b5f2016-12-04 17:17:39 -0800164 if (enabled) {
165 disable();
166 }
Jonathan Hart63eeac32016-06-20 15:55:16 -0700167
168 componentConfigService.unregisterProperties(getClass(), false);
169 }
170
Vinayak Tejankar3a409c62017-01-12 02:20:53 +0530171 private boolean handle(Ethernet eth) {
Jonathan Hart63eeac32016-06-20 15:55:16 -0700172 checkNotNull(eth);
Vinayak Tejankar3a409c62017-01-12 02:20:53 +0530173 // If the DirectHostManager is not enabled and the
174 // packets are different from what we expect just
175 // skip them.
176 if (!enabled || (eth.getEtherType() != Ethernet.TYPE_IPV6
177 && eth.getEtherType() != Ethernet.TYPE_IPV4)) {
178 return false;
Jonathan Hart63eeac32016-06-20 15:55:16 -0700179 }
Vinayak Tejankar3a409c62017-01-12 02:20:53 +0530180 // According to the type we set the destIp.
181 IpAddress dstIp;
182 if (eth.getEtherType() == Ethernet.TYPE_IPV4) {
183 IPv4 ip = (IPv4) eth.getPayload();
184 dstIp = IpAddress.valueOf(ip.getDestinationAddress());
185 } else {
186 IPv6 ip = (IPv6) eth.getPayload();
187 dstIp = IpAddress.valueOf(INET6, ip.getDestinationAddress());
188 }
189 // Looking for a candidate output port.
Jonathan Hart63eeac32016-06-20 15:55:16 -0700190 Interface egressInterface = interfaceService.getMatchingInterface(dstIp);
191
192 if (egressInterface == null) {
193 log.info("No egress interface found for {}", dstIp);
Vinayak Tejankar3a409c62017-01-12 02:20:53 +0530194 return false;
Jonathan Hart63eeac32016-06-20 15:55:16 -0700195 }
Vinayak Tejankar3a409c62017-01-12 02:20:53 +0530196 // Looking for the destination mac.
Jonathan Hart63eeac32016-06-20 15:55:16 -0700197 Optional<Host> host = hostService.getHostsByIp(dstIp).stream()
198 .filter(h -> h.location().equals(egressInterface.connectPoint()))
199 .filter(h -> h.vlan().equals(egressInterface.vlan()))
200 .findAny();
Vinayak Tejankar3a409c62017-01-12 02:20:53 +0530201 // If we don't have a destination we start the monitoring
202 // and we queue the packets waiting for a destination.
Jonathan Hart63eeac32016-06-20 15:55:16 -0700203 if (host.isPresent()) {
Vinayak Tejankar3a409c62017-01-12 02:20:53 +0530204 transformAndSend(
205 (IP) eth.getPayload(),
206 eth.getEtherType(),
207 egressInterface,
208 host.get().mac()
209 );
Jonathan Hart63eeac32016-06-20 15:55:16 -0700210 } else {
211 hostService.startMonitoringIp(dstIp);
212 ipPacketCache.asMap().compute(dstIp, (ip, queue) -> {
213 if (queue == null) {
Vinayak Tejankar3a409c62017-01-12 02:20:53 +0530214 queue = new ConcurrentLinkedQueue<>();
Jonathan Hart63eeac32016-06-20 15:55:16 -0700215 }
Vinayak Tejankar3a409c62017-01-12 02:20:53 +0530216 queue.add((IP) eth.getPayload());
Jonathan Hart63eeac32016-06-20 15:55:16 -0700217 return queue;
218 });
219 }
Vinayak Tejankar3a409c62017-01-12 02:20:53 +0530220
221 return true;
Jonathan Hart63eeac32016-06-20 15:55:16 -0700222 }
223
Vinayak Tejankar3a409c62017-01-12 02:20:53 +0530224 private void transformAndSend(IP ip, short ethType,
225 Interface egressInterface,
226 MacAddress macAddress) {
227 // Base processing for IPv4
228 if (ethType == Ethernet.TYPE_IPV4) {
229 IPv4 ipv4 = (IPv4) ip;
230 ipv4.setTtl((byte) (ipv4.getTtl() - 1));
231 ipv4.setChecksum((short) 0);
232 // Base processing for IPv6.
233 } else {
234 IPv6 ipv6 = (IPv6) ip;
235 ipv6.setHopLimit((byte) (ipv6.getHopLimit() - 1));
236 ipv6.resetChecksum();
237 }
238 // Sends and serializes.
Jonathan Hart63eeac32016-06-20 15:55:16 -0700239 Ethernet eth = new Ethernet();
240 eth.setDestinationMACAddress(macAddress);
241 eth.setSourceMACAddress(egressInterface.mac());
Vinayak Tejankar3a409c62017-01-12 02:20:53 +0530242 eth.setEtherType(ethType);
243 eth.setPayload(ip);
Jonathan Hart63eeac32016-06-20 15:55:16 -0700244 if (!egressInterface.vlan().equals(VlanId.NONE)) {
245 eth.setVlanID(egressInterface.vlan().toShort());
246 }
Jonathan Hart63eeac32016-06-20 15:55:16 -0700247 send(eth, egressInterface.connectPoint());
248 }
249
250 private void send(Ethernet eth, ConnectPoint cp) {
251 OutboundPacket packet = new DefaultOutboundPacket(cp.deviceId(),
252 DefaultTrafficTreatment.builder().setOutput(cp.port()).build(), ByteBuffer.wrap(eth.serialize()));
253 packetService.emit(packet);
254 }
255
256 private void sendQueued(IpAddress ipAddress, MacAddress macAddress) {
257 log.debug("Sending queued packets for {} ({})", ipAddress, macAddress);
258 ipPacketCache.asMap().computeIfPresent(ipAddress, (ip, packets) -> {
Vinayak Tejankar3a409c62017-01-12 02:20:53 +0530259 packets.forEach(ipPackets -> {
Jonathan Hart63eeac32016-06-20 15:55:16 -0700260 Interface egressInterface = interfaceService.getMatchingInterface(ipAddress);
261
262 if (egressInterface == null) {
263 log.info("No egress interface found for {}", ipAddress);
264 return;
265 }
266
Vinayak Tejankar3a409c62017-01-12 02:20:53 +0530267 // According to the type of the address we set proper
268 // protocol.
269 transformAndSend(
270 ipPackets,
271 ipAddress.isIp4() ? Ethernet.TYPE_IPV4 : Ethernet.TYPE_IPV6,
272 egressInterface,
273 macAddress
274 );
Jonathan Hart63eeac32016-06-20 15:55:16 -0700275 });
276 return null;
277 });
278 }
279
280 private class InternalPacketProcessor implements PacketProcessor {
281
282 @Override
283 public void process(PacketContext context) {
284 if (context.isHandled()) {
285 return;
286 }
287
288 if (interfaceService.getInterfacesByPort(context.inPacket().receivedFrom()).isEmpty()) {
289 // Don't handle packets that don't come from one of our configured interfaces
290 return;
291 }
292
293 Ethernet eth = context.inPacket().parsed();
294 if (eth == null) {
295 return;
296 }
297
Vinayak Tejankar3a409c62017-01-12 02:20:53 +0530298 if (!handle(eth)) {
299 return;
300 }
Jonathan Hart63eeac32016-06-20 15:55:16 -0700301
302 context.block();
303 }
304 }
305
306 private class InternalHostListener implements HostListener {
307 @Override
308 public void event(HostEvent event) {
309 switch (event.type()) {
310 case HOST_ADDED:
311 event.subject().ipAddresses().forEach(ip ->
312 DirectHostManager.this.sendQueued(ip, event.subject().mac()));
313 break;
314 case HOST_REMOVED:
315 case HOST_UPDATED:
316 case HOST_MOVED:
317 default:
318 break;
319 }
320 }
321 }
322}