blob: da62a5beecc10cca4cc69c31b3ecb7d10b91e33e [file] [log] [blame]
Jonathan Hart63eeac32016-06-20 15:55:16 -07001/*
Brian O'Connora09fe5b2017-08-03 21:12:30 -07002 * Copyright 2017-present Open Networking Foundation
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;
Jonathan Hart63eeac32016-06-20 15:55:16 -070021import org.onlab.packet.EthType;
22import org.onlab.packet.Ethernet;
Ray Milkeyd84f89b2018-08-17 14:54:17 -070023import org.onlab.packet.IP;
Jonathan Hart63eeac32016-06-20 15:55:16 -070024import org.onlab.packet.IPv4;
Vinayak Tejankar3a409c62017-01-12 02:20:53 +053025import org.onlab.packet.IPv6;
Jonathan Hart63eeac32016-06-20 15:55:16 -070026import org.onlab.packet.IpAddress;
27import org.onlab.packet.MacAddress;
28import org.onlab.packet.VlanId;
29import org.onlab.util.Tools;
30import org.onosproject.cfg.ComponentConfigService;
31import org.onosproject.core.ApplicationId;
32import org.onosproject.core.CoreService;
Jonathan Hart63eeac32016-06-20 15:55:16 -070033import org.onosproject.net.ConnectPoint;
34import org.onosproject.net.Host;
35import org.onosproject.net.flow.DefaultTrafficSelector;
36import org.onosproject.net.flow.DefaultTrafficTreatment;
37import org.onosproject.net.flow.TrafficSelector;
38import org.onosproject.net.host.HostEvent;
39import org.onosproject.net.host.HostListener;
40import org.onosproject.net.host.HostService;
Ray Milkeyd84f89b2018-08-17 14:54:17 -070041import org.onosproject.net.intf.Interface;
42import org.onosproject.net.intf.InterfaceService;
Jonathan Hart63eeac32016-06-20 15:55:16 -070043import org.onosproject.net.packet.DefaultOutboundPacket;
44import org.onosproject.net.packet.OutboundPacket;
45import org.onosproject.net.packet.PacketContext;
46import org.onosproject.net.packet.PacketPriority;
47import org.onosproject.net.packet.PacketProcessor;
48import org.onosproject.net.packet.PacketService;
49import org.osgi.service.component.ComponentContext;
Ray Milkeyd84f89b2018-08-17 14:54:17 -070050import org.osgi.service.component.annotations.Activate;
51import org.osgi.service.component.annotations.Component;
52import org.osgi.service.component.annotations.Deactivate;
53import org.osgi.service.component.annotations.Modified;
54import org.osgi.service.component.annotations.Reference;
55import org.osgi.service.component.annotations.ReferenceCardinality;
Jonathan Hart63eeac32016-06-20 15:55:16 -070056import org.slf4j.Logger;
57import org.slf4j.LoggerFactory;
58
59import java.nio.ByteBuffer;
60import java.util.Optional;
61import java.util.Queue;
62import java.util.concurrent.ConcurrentLinkedQueue;
63import java.util.concurrent.TimeUnit;
64
65import static com.google.common.base.Preconditions.checkNotNull;
Vinayak Tejankar3a409c62017-01-12 02:20:53 +053066import static org.onlab.packet.IpAddress.Version.INET6;
Jonathan Hart63eeac32016-06-20 15:55:16 -070067
68/**
69 * Reactively handles sending packets to hosts that are directly connected to
70 * router interfaces.
71 */
72@Component(immediate = true, enabled = false)
73public class DirectHostManager {
74
75 private Logger log = LoggerFactory.getLogger(getClass());
76
Ray Milkeyd84f89b2018-08-17 14:54:17 -070077 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Jonathan Hart63eeac32016-06-20 15:55:16 -070078 protected PacketService packetService;
79
Ray Milkeyd84f89b2018-08-17 14:54:17 -070080 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Jonathan Hart63eeac32016-06-20 15:55:16 -070081 protected InterfaceService interfaceService;
82
Ray Milkeyd84f89b2018-08-17 14:54:17 -070083 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Jonathan Hart63eeac32016-06-20 15:55:16 -070084 protected HostService hostService;
85
Ray Milkeyd84f89b2018-08-17 14:54:17 -070086 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Jonathan Hart63eeac32016-06-20 15:55:16 -070087 protected CoreService coreService;
88
Ray Milkeyd84f89b2018-08-17 14:54:17 -070089 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Jonathan Hart63eeac32016-06-20 15:55:16 -070090 protected ComponentConfigService componentConfigService;
91
92 private static final boolean DEFAULT_ENABLED = false;
93
Ray Milkeyd84f89b2018-08-17 14:54:17 -070094 //@Property(name = "enabled", boolValue = DEFAULT_ENABLED,
95 // label = "Enable reactive directly-connected host processing")
Jonathan Hart63eeac32016-06-20 15:55:16 -070096 private volatile boolean enabled = DEFAULT_ENABLED;
97
98 private static final String APP_NAME = "org.onosproject.directhost";
99
100 private static final long MAX_QUEUED_PACKETS = 10000;
101 private static final long MAX_QUEUE_DURATION = 2; // seconds
102
103 private ApplicationId appId;
104
105 private InternalPacketProcessor packetProcessor = new InternalPacketProcessor();
106 private InternalHostListener hostListener = new InternalHostListener();
107
Vinayak Tejankar3a409c62017-01-12 02:20:53 +0530108 private Cache<IpAddress, Queue<IP>> ipPacketCache = CacheBuilder.newBuilder()
109 .weigher((IpAddress key, Queue<IP> value) -> value.size())
Jonathan Hart63eeac32016-06-20 15:55:16 -0700110 .maximumWeight(MAX_QUEUED_PACKETS)
111 .expireAfterAccess(MAX_QUEUE_DURATION, TimeUnit.SECONDS)
112 .build();
113
114 @Activate
115 public void activate(ComponentContext context) {
116 componentConfigService.registerProperties(getClass());
Jonathan Hart63eeac32016-06-20 15:55:16 -0700117 appId = coreService.registerApplication(APP_NAME);
Charles Chan00d8b5f2016-12-04 17:17:39 -0800118 modified(context);
Jonathan Hart63eeac32016-06-20 15:55:16 -0700119 }
120
121 @Modified
122 private void modified(ComponentContext context) {
123 Boolean boolEnabled = Tools.isPropertyEnabled(context.getProperties(), "enabled");
124 if (boolEnabled != null) {
125 if (enabled && !boolEnabled) {
126 enabled = false;
127 disable();
128 } else if (!enabled && boolEnabled) {
129 enabled = true;
130 enable();
131 }
132 }
133 }
134
135 private void enable() {
136 hostService.addListener(hostListener);
137 packetService.addProcessor(packetProcessor, PacketProcessor.director(3));
Vinayak Tejankar3a409c62017-01-12 02:20:53 +0530138 // Requests packets for IPv4 traffic.
Jonathan Hart63eeac32016-06-20 15:55:16 -0700139 TrafficSelector selector = DefaultTrafficSelector.builder()
140 .matchEthType(EthType.EtherType.IPV4.ethType().toShort()).build();
141 packetService.requestPackets(selector, PacketPriority.REACTIVE, appId, Optional.empty());
Vinayak Tejankar3a409c62017-01-12 02:20:53 +0530142 // Requests packets for IPv6 traffic.
143 selector = DefaultTrafficSelector.builder()
144 .matchEthType(EthType.EtherType.IPV6.ethType().toShort()).build();
145 packetService.requestPackets(selector, PacketPriority.REACTIVE, appId, Optional.empty());
Jonathan Hart63eeac32016-06-20 15:55:16 -0700146 }
147
148 private void disable() {
149 packetService.removeProcessor(packetProcessor);
150 hostService.removeListener(hostListener);
Vinayak Tejankar3a409c62017-01-12 02:20:53 +0530151 // Withdraws IPv4 request.
Jonathan Hart63eeac32016-06-20 15:55:16 -0700152 TrafficSelector selector = DefaultTrafficSelector.builder()
153 .matchEthType(EthType.EtherType.IPV4.ethType().toShort()).build();
154 packetService.cancelPackets(selector, PacketPriority.REACTIVE, appId, Optional.empty());
Vinayak Tejankar3a409c62017-01-12 02:20:53 +0530155 // Withdraws IPv6 request.
156 selector = DefaultTrafficSelector.builder()
157 .matchEthType(EthType.EtherType.IPV6.ethType().toShort()).build();
158 packetService.cancelPackets(selector, PacketPriority.REACTIVE, appId, Optional.empty());
Jonathan Hart63eeac32016-06-20 15:55:16 -0700159 }
160
161 @Deactivate
162 public void deactivate() {
Charles Chan00d8b5f2016-12-04 17:17:39 -0800163 if (enabled) {
164 disable();
165 }
Jonathan Hart63eeac32016-06-20 15:55:16 -0700166
167 componentConfigService.unregisterProperties(getClass(), false);
168 }
169
Vinayak Tejankar3a409c62017-01-12 02:20:53 +0530170 private boolean handle(Ethernet eth) {
Jonathan Hart63eeac32016-06-20 15:55:16 -0700171 checkNotNull(eth);
Vinayak Tejankar3a409c62017-01-12 02:20:53 +0530172 // If the DirectHostManager is not enabled and the
173 // packets are different from what we expect just
174 // skip them.
175 if (!enabled || (eth.getEtherType() != Ethernet.TYPE_IPV6
176 && eth.getEtherType() != Ethernet.TYPE_IPV4)) {
177 return false;
Jonathan Hart63eeac32016-06-20 15:55:16 -0700178 }
Vinayak Tejankar3a409c62017-01-12 02:20:53 +0530179 // According to the type we set the destIp.
180 IpAddress dstIp;
181 if (eth.getEtherType() == Ethernet.TYPE_IPV4) {
182 IPv4 ip = (IPv4) eth.getPayload();
183 dstIp = IpAddress.valueOf(ip.getDestinationAddress());
184 } else {
185 IPv6 ip = (IPv6) eth.getPayload();
186 dstIp = IpAddress.valueOf(INET6, ip.getDestinationAddress());
187 }
188 // Looking for a candidate output port.
Jonathan Hart63eeac32016-06-20 15:55:16 -0700189 Interface egressInterface = interfaceService.getMatchingInterface(dstIp);
190
191 if (egressInterface == null) {
192 log.info("No egress interface found for {}", dstIp);
Vinayak Tejankar3a409c62017-01-12 02:20:53 +0530193 return false;
Jonathan Hart63eeac32016-06-20 15:55:16 -0700194 }
Vinayak Tejankar3a409c62017-01-12 02:20:53 +0530195 // Looking for the destination mac.
Jonathan Hart63eeac32016-06-20 15:55:16 -0700196 Optional<Host> host = hostService.getHostsByIp(dstIp).stream()
197 .filter(h -> h.location().equals(egressInterface.connectPoint()))
198 .filter(h -> h.vlan().equals(egressInterface.vlan()))
199 .findAny();
Vinayak Tejankar3a409c62017-01-12 02:20:53 +0530200 // If we don't have a destination we start the monitoring
201 // and we queue the packets waiting for a destination.
Jonathan Hart63eeac32016-06-20 15:55:16 -0700202 if (host.isPresent()) {
Vinayak Tejankar3a409c62017-01-12 02:20:53 +0530203 transformAndSend(
204 (IP) eth.getPayload(),
205 eth.getEtherType(),
206 egressInterface,
207 host.get().mac()
208 );
Jonathan Hart63eeac32016-06-20 15:55:16 -0700209 } else {
210 hostService.startMonitoringIp(dstIp);
211 ipPacketCache.asMap().compute(dstIp, (ip, queue) -> {
212 if (queue == null) {
Vinayak Tejankar3a409c62017-01-12 02:20:53 +0530213 queue = new ConcurrentLinkedQueue<>();
Jonathan Hart63eeac32016-06-20 15:55:16 -0700214 }
Vinayak Tejankar3a409c62017-01-12 02:20:53 +0530215 queue.add((IP) eth.getPayload());
Jonathan Hart63eeac32016-06-20 15:55:16 -0700216 return queue;
217 });
218 }
Vinayak Tejankar3a409c62017-01-12 02:20:53 +0530219
220 return true;
Jonathan Hart63eeac32016-06-20 15:55:16 -0700221 }
222
Vinayak Tejankar3a409c62017-01-12 02:20:53 +0530223 private void transformAndSend(IP ip, short ethType,
224 Interface egressInterface,
225 MacAddress macAddress) {
226 // Base processing for IPv4
227 if (ethType == Ethernet.TYPE_IPV4) {
228 IPv4 ipv4 = (IPv4) ip;
229 ipv4.setTtl((byte) (ipv4.getTtl() - 1));
230 ipv4.setChecksum((short) 0);
231 // Base processing for IPv6.
232 } else {
233 IPv6 ipv6 = (IPv6) ip;
234 ipv6.setHopLimit((byte) (ipv6.getHopLimit() - 1));
235 ipv6.resetChecksum();
236 }
237 // Sends and serializes.
Jonathan Hart63eeac32016-06-20 15:55:16 -0700238 Ethernet eth = new Ethernet();
239 eth.setDestinationMACAddress(macAddress);
240 eth.setSourceMACAddress(egressInterface.mac());
Vinayak Tejankar3a409c62017-01-12 02:20:53 +0530241 eth.setEtherType(ethType);
242 eth.setPayload(ip);
Jonathan Hart63eeac32016-06-20 15:55:16 -0700243 if (!egressInterface.vlan().equals(VlanId.NONE)) {
244 eth.setVlanID(egressInterface.vlan().toShort());
245 }
Jonathan Hart63eeac32016-06-20 15:55:16 -0700246 send(eth, egressInterface.connectPoint());
247 }
248
249 private void send(Ethernet eth, ConnectPoint cp) {
250 OutboundPacket packet = new DefaultOutboundPacket(cp.deviceId(),
251 DefaultTrafficTreatment.builder().setOutput(cp.port()).build(), ByteBuffer.wrap(eth.serialize()));
252 packetService.emit(packet);
253 }
254
255 private void sendQueued(IpAddress ipAddress, MacAddress macAddress) {
256 log.debug("Sending queued packets for {} ({})", ipAddress, macAddress);
257 ipPacketCache.asMap().computeIfPresent(ipAddress, (ip, packets) -> {
Vinayak Tejankar3a409c62017-01-12 02:20:53 +0530258 packets.forEach(ipPackets -> {
Jonathan Hart63eeac32016-06-20 15:55:16 -0700259 Interface egressInterface = interfaceService.getMatchingInterface(ipAddress);
260
261 if (egressInterface == null) {
262 log.info("No egress interface found for {}", ipAddress);
263 return;
264 }
265
Vinayak Tejankar3a409c62017-01-12 02:20:53 +0530266 // According to the type of the address we set proper
267 // protocol.
268 transformAndSend(
269 ipPackets,
270 ipAddress.isIp4() ? Ethernet.TYPE_IPV4 : Ethernet.TYPE_IPV6,
271 egressInterface,
272 macAddress
273 );
Jonathan Hart63eeac32016-06-20 15:55:16 -0700274 });
275 return null;
276 });
277 }
278
279 private class InternalPacketProcessor implements PacketProcessor {
280
281 @Override
282 public void process(PacketContext context) {
283 if (context.isHandled()) {
284 return;
285 }
286
287 if (interfaceService.getInterfacesByPort(context.inPacket().receivedFrom()).isEmpty()) {
288 // Don't handle packets that don't come from one of our configured interfaces
289 return;
290 }
291
292 Ethernet eth = context.inPacket().parsed();
293 if (eth == null) {
294 return;
295 }
296
Vinayak Tejankar3a409c62017-01-12 02:20:53 +0530297 if (!handle(eth)) {
298 return;
299 }
Jonathan Hart63eeac32016-06-20 15:55:16 -0700300
301 context.block();
302 }
303 }
304
305 private class InternalHostListener implements HostListener {
306 @Override
307 public void event(HostEvent event) {
308 switch (event.type()) {
309 case HOST_ADDED:
310 event.subject().ipAddresses().forEach(ip ->
311 DirectHostManager.this.sendQueued(ip, event.subject().mac()));
312 break;
313 case HOST_REMOVED:
314 case HOST_UPDATED:
315 case HOST_MOVED:
316 default:
317 break;
318 }
319 }
320 }
321}