blob: d5c0d2754dcbb739c3780f6efdc6dc7f3d9b369d [file] [log] [blame]
Jonathan Hart63eeac32016-06-20 15:55:16 -07001/*
2 * Copyright 2016-present 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
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;
31import org.onlab.packet.Ip4Address;
32import org.onlab.packet.IpAddress;
33import org.onlab.packet.MacAddress;
34import org.onlab.packet.VlanId;
35import org.onlab.util.Tools;
36import org.onosproject.cfg.ComponentConfigService;
37import org.onosproject.core.ApplicationId;
38import org.onosproject.core.CoreService;
39import org.onosproject.incubator.net.intf.Interface;
40import org.onosproject.incubator.net.intf.InterfaceService;
41import org.onosproject.net.ConnectPoint;
42import org.onosproject.net.Host;
43import org.onosproject.net.flow.DefaultTrafficSelector;
44import org.onosproject.net.flow.DefaultTrafficTreatment;
45import org.onosproject.net.flow.TrafficSelector;
46import org.onosproject.net.host.HostEvent;
47import org.onosproject.net.host.HostListener;
48import org.onosproject.net.host.HostService;
49import org.onosproject.net.packet.DefaultOutboundPacket;
50import org.onosproject.net.packet.OutboundPacket;
51import org.onosproject.net.packet.PacketContext;
52import org.onosproject.net.packet.PacketPriority;
53import org.onosproject.net.packet.PacketProcessor;
54import org.onosproject.net.packet.PacketService;
55import org.osgi.service.component.ComponentContext;
56import 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;
66
67/**
68 * Reactively handles sending packets to hosts that are directly connected to
69 * router interfaces.
70 */
71@Component(immediate = true, enabled = false)
72public class DirectHostManager {
73
74 private Logger log = LoggerFactory.getLogger(getClass());
75
76 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
77 protected PacketService packetService;
78
79 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
80 protected InterfaceService interfaceService;
81
82 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
83 protected HostService hostService;
84
85 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
86 protected CoreService coreService;
87
88 @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
89 protected ComponentConfigService componentConfigService;
90
91 private static final boolean DEFAULT_ENABLED = false;
92
93 @Property(name = "enabled", boolValue = DEFAULT_ENABLED,
94 label = "Enable reactive directly-connected host processing")
95 private volatile boolean enabled = DEFAULT_ENABLED;
96
97 private static final String APP_NAME = "org.onosproject.directhost";
98
99 private static final long MAX_QUEUED_PACKETS = 10000;
100 private static final long MAX_QUEUE_DURATION = 2; // seconds
101
102 private ApplicationId appId;
103
104 private InternalPacketProcessor packetProcessor = new InternalPacketProcessor();
105 private InternalHostListener hostListener = new InternalHostListener();
106
107 private Cache<IpAddress, Queue<IPv4>> ipPacketCache = CacheBuilder.newBuilder()
108 .weigher((IpAddress key, Queue<IPv4> value) -> value.size())
109 .maximumWeight(MAX_QUEUED_PACKETS)
110 .expireAfterAccess(MAX_QUEUE_DURATION, TimeUnit.SECONDS)
111 .build();
112
113 @Activate
114 public void activate(ComponentContext context) {
115 componentConfigService.registerProperties(getClass());
Jonathan Hart63eeac32016-06-20 15:55:16 -0700116 appId = coreService.registerApplication(APP_NAME);
Charles Chan00d8b5f2016-12-04 17:17:39 -0800117 modified(context);
Jonathan Hart63eeac32016-06-20 15:55:16 -0700118 }
119
120 @Modified
121 private void modified(ComponentContext context) {
122 Boolean boolEnabled = Tools.isPropertyEnabled(context.getProperties(), "enabled");
123 if (boolEnabled != null) {
124 if (enabled && !boolEnabled) {
125 enabled = false;
126 disable();
127 } else if (!enabled && boolEnabled) {
128 enabled = true;
129 enable();
130 }
131 }
132 }
133
134 private void enable() {
135 hostService.addListener(hostListener);
136 packetService.addProcessor(packetProcessor, PacketProcessor.director(3));
137
138 TrafficSelector selector = DefaultTrafficSelector.builder()
139 .matchEthType(EthType.EtherType.IPV4.ethType().toShort()).build();
140 packetService.requestPackets(selector, PacketPriority.REACTIVE, appId, Optional.empty());
141 }
142
143 private void disable() {
144 packetService.removeProcessor(packetProcessor);
145 hostService.removeListener(hostListener);
146
147 TrafficSelector selector = DefaultTrafficSelector.builder()
148 .matchEthType(EthType.EtherType.IPV4.ethType().toShort()).build();
149 packetService.cancelPackets(selector, PacketPriority.REACTIVE, appId, Optional.empty());
150 }
151
152 @Deactivate
153 public void deactivate() {
Charles Chan00d8b5f2016-12-04 17:17:39 -0800154 if (enabled) {
155 disable();
156 }
Jonathan Hart63eeac32016-06-20 15:55:16 -0700157
158 componentConfigService.unregisterProperties(getClass(), false);
159 }
160
161 private void handle(Ethernet eth) {
162 checkNotNull(eth);
163
164 if (!(eth.getEtherType() == EthType.EtherType.IPV4.ethType().toShort())) {
165 return;
166 }
167
168 IPv4 ipv4 = (IPv4) eth.getPayload().clone();
169
170 Ip4Address dstIp = Ip4Address.valueOf(ipv4.getDestinationAddress());
171
172 Interface egressInterface = interfaceService.getMatchingInterface(dstIp);
173
174 if (egressInterface == null) {
175 log.info("No egress interface found for {}", dstIp);
176 return;
177 }
178
179 Optional<Host> host = hostService.getHostsByIp(dstIp).stream()
180 .filter(h -> h.location().equals(egressInterface.connectPoint()))
181 .filter(h -> h.vlan().equals(egressInterface.vlan()))
182 .findAny();
183
184 if (host.isPresent()) {
185 transformAndSend(ipv4, egressInterface, host.get().mac());
186 } else {
187 hostService.startMonitoringIp(dstIp);
188 ipPacketCache.asMap().compute(dstIp, (ip, queue) -> {
189 if (queue == null) {
190 queue = new ConcurrentLinkedQueue();
191 }
192 queue.add(ipv4);
193 return queue;
194 });
195 }
196 }
197
198 private void transformAndSend(IPv4 ipv4, Interface egressInterface, MacAddress macAddress) {
199
200 Ethernet eth = new Ethernet();
201 eth.setDestinationMACAddress(macAddress);
202 eth.setSourceMACAddress(egressInterface.mac());
203 eth.setEtherType(EthType.EtherType.IPV4.ethType().toShort());
204 eth.setPayload(ipv4);
205 if (!egressInterface.vlan().equals(VlanId.NONE)) {
206 eth.setVlanID(egressInterface.vlan().toShort());
207 }
208
209 ipv4.setTtl((byte) (ipv4.getTtl() - 1));
210 ipv4.setChecksum((short) 0);
211
212 send(eth, egressInterface.connectPoint());
213 }
214
215 private void send(Ethernet eth, ConnectPoint cp) {
216 OutboundPacket packet = new DefaultOutboundPacket(cp.deviceId(),
217 DefaultTrafficTreatment.builder().setOutput(cp.port()).build(), ByteBuffer.wrap(eth.serialize()));
218 packetService.emit(packet);
219 }
220
221 private void sendQueued(IpAddress ipAddress, MacAddress macAddress) {
222 log.debug("Sending queued packets for {} ({})", ipAddress, macAddress);
223 ipPacketCache.asMap().computeIfPresent(ipAddress, (ip, packets) -> {
224 packets.forEach(ipv4 -> {
225 Interface egressInterface = interfaceService.getMatchingInterface(ipAddress);
226
227 if (egressInterface == null) {
228 log.info("No egress interface found for {}", ipAddress);
229 return;
230 }
231
232 transformAndSend(ipv4, egressInterface, macAddress);
233 });
234 return null;
235 });
236 }
237
238 private class InternalPacketProcessor implements PacketProcessor {
239
240 @Override
241 public void process(PacketContext context) {
242 if (context.isHandled()) {
243 return;
244 }
245
246 if (interfaceService.getInterfacesByPort(context.inPacket().receivedFrom()).isEmpty()) {
247 // Don't handle packets that don't come from one of our configured interfaces
248 return;
249 }
250
251 Ethernet eth = context.inPacket().parsed();
252 if (eth == null) {
253 return;
254 }
255
256 handle(eth);
257
258 context.block();
259 }
260 }
261
262 private class InternalHostListener implements HostListener {
263 @Override
264 public void event(HostEvent event) {
265 switch (event.type()) {
266 case HOST_ADDED:
267 event.subject().ipAddresses().forEach(ip ->
268 DirectHostManager.this.sendQueued(ip, event.subject().mac()));
269 break;
270 case HOST_REMOVED:
271 case HOST_UPDATED:
272 case HOST_MOVED:
273 default:
274 break;
275 }
276 }
277 }
278}