blob: 7e0b2acedebf81295cfdbd185f5d53498c7ab0d0 [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());
116 modified(context);
117
118 appId = coreService.registerApplication(APP_NAME);
119
120 if (enabled) {
121 enable();
122 }
123 }
124
125 @Modified
126 private void modified(ComponentContext context) {
127 Boolean boolEnabled = Tools.isPropertyEnabled(context.getProperties(), "enabled");
128 if (boolEnabled != null) {
129 if (enabled && !boolEnabled) {
130 enabled = false;
131 disable();
132 } else if (!enabled && boolEnabled) {
133 enabled = true;
134 enable();
135 }
136 }
137 }
138
139 private void enable() {
140 hostService.addListener(hostListener);
141 packetService.addProcessor(packetProcessor, PacketProcessor.director(3));
142
143 TrafficSelector selector = DefaultTrafficSelector.builder()
144 .matchEthType(EthType.EtherType.IPV4.ethType().toShort()).build();
145 packetService.requestPackets(selector, PacketPriority.REACTIVE, appId, Optional.empty());
146 }
147
148 private void disable() {
149 packetService.removeProcessor(packetProcessor);
150 hostService.removeListener(hostListener);
151
152 TrafficSelector selector = DefaultTrafficSelector.builder()
153 .matchEthType(EthType.EtherType.IPV4.ethType().toShort()).build();
154 packetService.cancelPackets(selector, PacketPriority.REACTIVE, appId, Optional.empty());
155 }
156
157 @Deactivate
158 public void deactivate() {
159 disable();
160
161 componentConfigService.unregisterProperties(getClass(), false);
162 }
163
164 private void handle(Ethernet eth) {
165 checkNotNull(eth);
166
167 if (!(eth.getEtherType() == EthType.EtherType.IPV4.ethType().toShort())) {
168 return;
169 }
170
171 IPv4 ipv4 = (IPv4) eth.getPayload().clone();
172
173 Ip4Address dstIp = Ip4Address.valueOf(ipv4.getDestinationAddress());
174
175 Interface egressInterface = interfaceService.getMatchingInterface(dstIp);
176
177 if (egressInterface == null) {
178 log.info("No egress interface found for {}", dstIp);
179 return;
180 }
181
182 Optional<Host> host = hostService.getHostsByIp(dstIp).stream()
183 .filter(h -> h.location().equals(egressInterface.connectPoint()))
184 .filter(h -> h.vlan().equals(egressInterface.vlan()))
185 .findAny();
186
187 if (host.isPresent()) {
188 transformAndSend(ipv4, egressInterface, host.get().mac());
189 } else {
190 hostService.startMonitoringIp(dstIp);
191 ipPacketCache.asMap().compute(dstIp, (ip, queue) -> {
192 if (queue == null) {
193 queue = new ConcurrentLinkedQueue();
194 }
195 queue.add(ipv4);
196 return queue;
197 });
198 }
199 }
200
201 private void transformAndSend(IPv4 ipv4, Interface egressInterface, MacAddress macAddress) {
202
203 Ethernet eth = new Ethernet();
204 eth.setDestinationMACAddress(macAddress);
205 eth.setSourceMACAddress(egressInterface.mac());
206 eth.setEtherType(EthType.EtherType.IPV4.ethType().toShort());
207 eth.setPayload(ipv4);
208 if (!egressInterface.vlan().equals(VlanId.NONE)) {
209 eth.setVlanID(egressInterface.vlan().toShort());
210 }
211
212 ipv4.setTtl((byte) (ipv4.getTtl() - 1));
213 ipv4.setChecksum((short) 0);
214
215 send(eth, egressInterface.connectPoint());
216 }
217
218 private void send(Ethernet eth, ConnectPoint cp) {
219 OutboundPacket packet = new DefaultOutboundPacket(cp.deviceId(),
220 DefaultTrafficTreatment.builder().setOutput(cp.port()).build(), ByteBuffer.wrap(eth.serialize()));
221 packetService.emit(packet);
222 }
223
224 private void sendQueued(IpAddress ipAddress, MacAddress macAddress) {
225 log.debug("Sending queued packets for {} ({})", ipAddress, macAddress);
226 ipPacketCache.asMap().computeIfPresent(ipAddress, (ip, packets) -> {
227 packets.forEach(ipv4 -> {
228 Interface egressInterface = interfaceService.getMatchingInterface(ipAddress);
229
230 if (egressInterface == null) {
231 log.info("No egress interface found for {}", ipAddress);
232 return;
233 }
234
235 transformAndSend(ipv4, egressInterface, macAddress);
236 });
237 return null;
238 });
239 }
240
241 private class InternalPacketProcessor implements PacketProcessor {
242
243 @Override
244 public void process(PacketContext context) {
245 if (context.isHandled()) {
246 return;
247 }
248
249 if (interfaceService.getInterfacesByPort(context.inPacket().receivedFrom()).isEmpty()) {
250 // Don't handle packets that don't come from one of our configured interfaces
251 return;
252 }
253
254 Ethernet eth = context.inPacket().parsed();
255 if (eth == null) {
256 return;
257 }
258
259 handle(eth);
260
261 context.block();
262 }
263 }
264
265 private class InternalHostListener implements HostListener {
266 @Override
267 public void event(HostEvent event) {
268 switch (event.type()) {
269 case HOST_ADDED:
270 event.subject().ipAddresses().forEach(ip ->
271 DirectHostManager.this.sendQueued(ip, event.subject().mac()));
272 break;
273 case HOST_REMOVED:
274 case HOST_UPDATED:
275 case HOST_MOVED:
276 default:
277 break;
278 }
279 }
280 }
281}