blob: 934866589baea8a04771b4d2175d1a3b01b0bec1 [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;
Ray Milkey4694e062018-10-31 13:17:18 -070067import static org.onosproject.routing.impl.OsgiPropertyConstants.ENABLED;
68import static org.onosproject.routing.impl.OsgiPropertyConstants.ENABLED_DEFAULT;
Jonathan Hart63eeac32016-06-20 15:55:16 -070069
70/**
71 * Reactively handles sending packets to hosts that are directly connected to
72 * router interfaces.
73 */
Ray Milkey4694e062018-10-31 13:17:18 -070074@Component(
75 immediate = true,
76 enabled = false,
77 property = {
78 ENABLED + ":Boolean=" + ENABLED_DEFAULT
79 }
80)
Jonathan Hart63eeac32016-06-20 15:55:16 -070081public class DirectHostManager {
82
83 private Logger log = LoggerFactory.getLogger(getClass());
84
Ray Milkeyd84f89b2018-08-17 14:54:17 -070085 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Jonathan Hart63eeac32016-06-20 15:55:16 -070086 protected PacketService packetService;
87
Ray Milkeyd84f89b2018-08-17 14:54:17 -070088 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Jonathan Hart63eeac32016-06-20 15:55:16 -070089 protected InterfaceService interfaceService;
90
Ray Milkeyd84f89b2018-08-17 14:54:17 -070091 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Jonathan Hart63eeac32016-06-20 15:55:16 -070092 protected HostService hostService;
93
Ray Milkeyd84f89b2018-08-17 14:54:17 -070094 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Jonathan Hart63eeac32016-06-20 15:55:16 -070095 protected CoreService coreService;
96
Ray Milkeyd84f89b2018-08-17 14:54:17 -070097 @Reference(cardinality = ReferenceCardinality.MANDATORY)
Jonathan Hart63eeac32016-06-20 15:55:16 -070098 protected ComponentConfigService componentConfigService;
99
Ray Milkey4694e062018-10-31 13:17:18 -0700100 /** Enable reactive directly-connected host processing. */
101 private volatile boolean enabled = ENABLED_DEFAULT;
Jonathan Hart63eeac32016-06-20 15:55:16 -0700102
103 private static final String APP_NAME = "org.onosproject.directhost";
104
105 private static final long MAX_QUEUED_PACKETS = 10000;
106 private static final long MAX_QUEUE_DURATION = 2; // seconds
107
108 private ApplicationId appId;
109
110 private InternalPacketProcessor packetProcessor = new InternalPacketProcessor();
111 private InternalHostListener hostListener = new InternalHostListener();
112
Vinayak Tejankar3a409c62017-01-12 02:20:53 +0530113 private Cache<IpAddress, Queue<IP>> ipPacketCache = CacheBuilder.newBuilder()
114 .weigher((IpAddress key, Queue<IP> value) -> value.size())
Jonathan Hart63eeac32016-06-20 15:55:16 -0700115 .maximumWeight(MAX_QUEUED_PACKETS)
116 .expireAfterAccess(MAX_QUEUE_DURATION, TimeUnit.SECONDS)
117 .build();
118
119 @Activate
120 public void activate(ComponentContext context) {
121 componentConfigService.registerProperties(getClass());
Jonathan Hart63eeac32016-06-20 15:55:16 -0700122 appId = coreService.registerApplication(APP_NAME);
Charles Chan00d8b5f2016-12-04 17:17:39 -0800123 modified(context);
Jonathan Hart63eeac32016-06-20 15:55:16 -0700124 }
125
126 @Modified
127 private void modified(ComponentContext context) {
Ray Milkey4694e062018-10-31 13:17:18 -0700128 Boolean boolEnabled = Tools.isPropertyEnabled(context.getProperties(), ENABLED);
Jonathan Hart63eeac32016-06-20 15:55:16 -0700129 if (boolEnabled != null) {
130 if (enabled && !boolEnabled) {
131 enabled = false;
132 disable();
133 } else if (!enabled && boolEnabled) {
134 enabled = true;
135 enable();
136 }
137 }
138 }
139
140 private void enable() {
141 hostService.addListener(hostListener);
142 packetService.addProcessor(packetProcessor, PacketProcessor.director(3));
Vinayak Tejankar3a409c62017-01-12 02:20:53 +0530143 // Requests packets for IPv4 traffic.
Jonathan Hart63eeac32016-06-20 15:55:16 -0700144 TrafficSelector selector = DefaultTrafficSelector.builder()
145 .matchEthType(EthType.EtherType.IPV4.ethType().toShort()).build();
146 packetService.requestPackets(selector, PacketPriority.REACTIVE, appId, Optional.empty());
Vinayak Tejankar3a409c62017-01-12 02:20:53 +0530147 // Requests packets for IPv6 traffic.
148 selector = DefaultTrafficSelector.builder()
149 .matchEthType(EthType.EtherType.IPV6.ethType().toShort()).build();
150 packetService.requestPackets(selector, PacketPriority.REACTIVE, appId, Optional.empty());
Jonathan Hart63eeac32016-06-20 15:55:16 -0700151 }
152
153 private void disable() {
154 packetService.removeProcessor(packetProcessor);
155 hostService.removeListener(hostListener);
Vinayak Tejankar3a409c62017-01-12 02:20:53 +0530156 // Withdraws IPv4 request.
Jonathan Hart63eeac32016-06-20 15:55:16 -0700157 TrafficSelector selector = DefaultTrafficSelector.builder()
158 .matchEthType(EthType.EtherType.IPV4.ethType().toShort()).build();
159 packetService.cancelPackets(selector, PacketPriority.REACTIVE, appId, Optional.empty());
Vinayak Tejankar3a409c62017-01-12 02:20:53 +0530160 // Withdraws IPv6 request.
161 selector = DefaultTrafficSelector.builder()
162 .matchEthType(EthType.EtherType.IPV6.ethType().toShort()).build();
163 packetService.cancelPackets(selector, PacketPriority.REACTIVE, appId, Optional.empty());
Jonathan Hart63eeac32016-06-20 15:55:16 -0700164 }
165
166 @Deactivate
167 public void deactivate() {
Charles Chan00d8b5f2016-12-04 17:17:39 -0800168 if (enabled) {
169 disable();
170 }
Jonathan Hart63eeac32016-06-20 15:55:16 -0700171
172 componentConfigService.unregisterProperties(getClass(), false);
173 }
174
Vinayak Tejankar3a409c62017-01-12 02:20:53 +0530175 private boolean handle(Ethernet eth) {
Jonathan Hart63eeac32016-06-20 15:55:16 -0700176 checkNotNull(eth);
Vinayak Tejankar3a409c62017-01-12 02:20:53 +0530177 // If the DirectHostManager is not enabled and the
178 // packets are different from what we expect just
179 // skip them.
180 if (!enabled || (eth.getEtherType() != Ethernet.TYPE_IPV6
181 && eth.getEtherType() != Ethernet.TYPE_IPV4)) {
182 return false;
Jonathan Hart63eeac32016-06-20 15:55:16 -0700183 }
Vinayak Tejankar3a409c62017-01-12 02:20:53 +0530184 // According to the type we set the destIp.
185 IpAddress dstIp;
186 if (eth.getEtherType() == Ethernet.TYPE_IPV4) {
187 IPv4 ip = (IPv4) eth.getPayload();
188 dstIp = IpAddress.valueOf(ip.getDestinationAddress());
189 } else {
190 IPv6 ip = (IPv6) eth.getPayload();
191 dstIp = IpAddress.valueOf(INET6, ip.getDestinationAddress());
192 }
193 // Looking for a candidate output port.
Jonathan Hart63eeac32016-06-20 15:55:16 -0700194 Interface egressInterface = interfaceService.getMatchingInterface(dstIp);
195
196 if (egressInterface == null) {
197 log.info("No egress interface found for {}", dstIp);
Vinayak Tejankar3a409c62017-01-12 02:20:53 +0530198 return false;
Jonathan Hart63eeac32016-06-20 15:55:16 -0700199 }
Vinayak Tejankar3a409c62017-01-12 02:20:53 +0530200 // Looking for the destination mac.
Jonathan Hart63eeac32016-06-20 15:55:16 -0700201 Optional<Host> host = hostService.getHostsByIp(dstIp).stream()
202 .filter(h -> h.location().equals(egressInterface.connectPoint()))
203 .filter(h -> h.vlan().equals(egressInterface.vlan()))
204 .findAny();
Vinayak Tejankar3a409c62017-01-12 02:20:53 +0530205 // If we don't have a destination we start the monitoring
206 // and we queue the packets waiting for a destination.
Jonathan Hart63eeac32016-06-20 15:55:16 -0700207 if (host.isPresent()) {
Vinayak Tejankar3a409c62017-01-12 02:20:53 +0530208 transformAndSend(
209 (IP) eth.getPayload(),
210 eth.getEtherType(),
211 egressInterface,
212 host.get().mac()
213 );
Jonathan Hart63eeac32016-06-20 15:55:16 -0700214 } else {
215 hostService.startMonitoringIp(dstIp);
216 ipPacketCache.asMap().compute(dstIp, (ip, queue) -> {
217 if (queue == null) {
Vinayak Tejankar3a409c62017-01-12 02:20:53 +0530218 queue = new ConcurrentLinkedQueue<>();
Jonathan Hart63eeac32016-06-20 15:55:16 -0700219 }
Vinayak Tejankar3a409c62017-01-12 02:20:53 +0530220 queue.add((IP) eth.getPayload());
Jonathan Hart63eeac32016-06-20 15:55:16 -0700221 return queue;
222 });
223 }
Vinayak Tejankar3a409c62017-01-12 02:20:53 +0530224
225 return true;
Jonathan Hart63eeac32016-06-20 15:55:16 -0700226 }
227
Vinayak Tejankar3a409c62017-01-12 02:20:53 +0530228 private void transformAndSend(IP ip, short ethType,
229 Interface egressInterface,
230 MacAddress macAddress) {
231 // Base processing for IPv4
232 if (ethType == Ethernet.TYPE_IPV4) {
233 IPv4 ipv4 = (IPv4) ip;
234 ipv4.setTtl((byte) (ipv4.getTtl() - 1));
235 ipv4.setChecksum((short) 0);
236 // Base processing for IPv6.
237 } else {
238 IPv6 ipv6 = (IPv6) ip;
239 ipv6.setHopLimit((byte) (ipv6.getHopLimit() - 1));
240 ipv6.resetChecksum();
241 }
242 // Sends and serializes.
Jonathan Hart63eeac32016-06-20 15:55:16 -0700243 Ethernet eth = new Ethernet();
244 eth.setDestinationMACAddress(macAddress);
245 eth.setSourceMACAddress(egressInterface.mac());
Vinayak Tejankar3a409c62017-01-12 02:20:53 +0530246 eth.setEtherType(ethType);
247 eth.setPayload(ip);
Jonathan Hart63eeac32016-06-20 15:55:16 -0700248 if (!egressInterface.vlan().equals(VlanId.NONE)) {
249 eth.setVlanID(egressInterface.vlan().toShort());
250 }
Jonathan Hart63eeac32016-06-20 15:55:16 -0700251 send(eth, egressInterface.connectPoint());
252 }
253
254 private void send(Ethernet eth, ConnectPoint cp) {
255 OutboundPacket packet = new DefaultOutboundPacket(cp.deviceId(),
256 DefaultTrafficTreatment.builder().setOutput(cp.port()).build(), ByteBuffer.wrap(eth.serialize()));
257 packetService.emit(packet);
258 }
259
260 private void sendQueued(IpAddress ipAddress, MacAddress macAddress) {
261 log.debug("Sending queued packets for {} ({})", ipAddress, macAddress);
262 ipPacketCache.asMap().computeIfPresent(ipAddress, (ip, packets) -> {
Vinayak Tejankar3a409c62017-01-12 02:20:53 +0530263 packets.forEach(ipPackets -> {
Jonathan Hart63eeac32016-06-20 15:55:16 -0700264 Interface egressInterface = interfaceService.getMatchingInterface(ipAddress);
265
266 if (egressInterface == null) {
267 log.info("No egress interface found for {}", ipAddress);
268 return;
269 }
270
Vinayak Tejankar3a409c62017-01-12 02:20:53 +0530271 // According to the type of the address we set proper
272 // protocol.
273 transformAndSend(
274 ipPackets,
275 ipAddress.isIp4() ? Ethernet.TYPE_IPV4 : Ethernet.TYPE_IPV6,
276 egressInterface,
277 macAddress
278 );
Jonathan Hart63eeac32016-06-20 15:55:16 -0700279 });
280 return null;
281 });
282 }
283
284 private class InternalPacketProcessor implements PacketProcessor {
285
286 @Override
287 public void process(PacketContext context) {
288 if (context.isHandled()) {
289 return;
290 }
291
292 if (interfaceService.getInterfacesByPort(context.inPacket().receivedFrom()).isEmpty()) {
293 // Don't handle packets that don't come from one of our configured interfaces
294 return;
295 }
296
297 Ethernet eth = context.inPacket().parsed();
298 if (eth == null) {
299 return;
300 }
301
Vinayak Tejankar3a409c62017-01-12 02:20:53 +0530302 if (!handle(eth)) {
303 return;
304 }
Jonathan Hart63eeac32016-06-20 15:55:16 -0700305
306 context.block();
307 }
308 }
309
310 private class InternalHostListener implements HostListener {
311 @Override
312 public void event(HostEvent event) {
313 switch (event.type()) {
314 case HOST_ADDED:
315 event.subject().ipAddresses().forEach(ip ->
316 DirectHostManager.this.sendQueued(ip, event.subject().mac()));
317 break;
318 case HOST_REMOVED:
319 case HOST_UPDATED:
320 case HOST_MOVED:
321 default:
322 break;
323 }
324 }
325 }
326}