Jonathan Hart
Committed by Gerrit Code Review

Add ability for vRouter to reactively send packets to directly connected hosts.

Change-Id: I652ad33acf95b5ef5806699135382d8be1260781
1 +/*
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 +
17 +package org.onosproject.routing.impl;
18 +
19 +import com.google.common.cache.Cache;
20 +import com.google.common.cache.CacheBuilder;
21 +import org.apache.felix.scr.annotations.Activate;
22 +import org.apache.felix.scr.annotations.Component;
23 +import org.apache.felix.scr.annotations.Deactivate;
24 +import org.apache.felix.scr.annotations.Modified;
25 +import org.apache.felix.scr.annotations.Property;
26 +import org.apache.felix.scr.annotations.Reference;
27 +import org.apache.felix.scr.annotations.ReferenceCardinality;
28 +import org.onlab.packet.EthType;
29 +import org.onlab.packet.Ethernet;
30 +import org.onlab.packet.IPv4;
31 +import org.onlab.packet.Ip4Address;
32 +import org.onlab.packet.IpAddress;
33 +import org.onlab.packet.MacAddress;
34 +import org.onlab.packet.VlanId;
35 +import org.onlab.util.Tools;
36 +import org.onosproject.cfg.ComponentConfigService;
37 +import org.onosproject.core.ApplicationId;
38 +import org.onosproject.core.CoreService;
39 +import org.onosproject.incubator.net.intf.Interface;
40 +import org.onosproject.incubator.net.intf.InterfaceService;
41 +import org.onosproject.net.ConnectPoint;
42 +import org.onosproject.net.Host;
43 +import org.onosproject.net.flow.DefaultTrafficSelector;
44 +import org.onosproject.net.flow.DefaultTrafficTreatment;
45 +import org.onosproject.net.flow.TrafficSelector;
46 +import org.onosproject.net.host.HostEvent;
47 +import org.onosproject.net.host.HostListener;
48 +import org.onosproject.net.host.HostService;
49 +import org.onosproject.net.packet.DefaultOutboundPacket;
50 +import org.onosproject.net.packet.OutboundPacket;
51 +import org.onosproject.net.packet.PacketContext;
52 +import org.onosproject.net.packet.PacketPriority;
53 +import org.onosproject.net.packet.PacketProcessor;
54 +import org.onosproject.net.packet.PacketService;
55 +import org.osgi.service.component.ComponentContext;
56 +import org.slf4j.Logger;
57 +import org.slf4j.LoggerFactory;
58 +
59 +import java.nio.ByteBuffer;
60 +import java.util.Optional;
61 +import java.util.Queue;
62 +import java.util.concurrent.ConcurrentLinkedQueue;
63 +import java.util.concurrent.TimeUnit;
64 +
65 +import 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)
72 +public 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 +}
...@@ -51,8 +51,10 @@ public class Vrouter { ...@@ -51,8 +51,10 @@ public class Vrouter {
51 .add("org.onosproject.routing.fpm.FpmManager") 51 .add("org.onosproject.routing.fpm.FpmManager")
52 .add("org.onosproject.routing.impl.SingleSwitchFibInstaller") 52 .add("org.onosproject.routing.impl.SingleSwitchFibInstaller")
53 .add("org.onosproject.routing.impl.ControlPlaneRedirectManager") 53 .add("org.onosproject.routing.impl.ControlPlaneRedirectManager")
54 + .add("org.onosproject.routing.impl.DirectHostManager")
54 .build(); 55 .build();
55 56
57 +
56 @Activate 58 @Activate
57 protected void activate() { 59 protected void activate() {
58 appId = coreService.registerApplication(APP_NAME); 60 appId = coreService.registerApplication(APP_NAME);
......
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
16 package org.onosproject.driver.pipeline; 16 package org.onosproject.driver.pipeline;
17 17
18 import org.onlab.osgi.ServiceDirectory; 18 import org.onlab.osgi.ServiceDirectory;
19 +import org.onlab.packet.EthType;
19 import org.onlab.packet.Ethernet; 20 import org.onlab.packet.Ethernet;
20 import org.onlab.packet.IpPrefix; 21 import org.onlab.packet.IpPrefix;
21 import org.onlab.packet.VlanId; 22 import org.onlab.packet.VlanId;
...@@ -23,6 +24,7 @@ import org.onlab.util.KryoNamespace; ...@@ -23,6 +24,7 @@ import org.onlab.util.KryoNamespace;
23 import org.onosproject.core.ApplicationId; 24 import org.onosproject.core.ApplicationId;
24 import org.onosproject.core.CoreService; 25 import org.onosproject.core.CoreService;
25 import org.onosproject.net.DeviceId; 26 import org.onosproject.net.DeviceId;
27 +import org.onosproject.net.PortNumber;
26 import org.onosproject.net.behaviour.NextGroup; 28 import org.onosproject.net.behaviour.NextGroup;
27 import org.onosproject.net.behaviour.Pipeliner; 29 import org.onosproject.net.behaviour.Pipeliner;
28 import org.onosproject.net.behaviour.PipelinerContext; 30 import org.onosproject.net.behaviour.PipelinerContext;
...@@ -336,6 +338,16 @@ public class SoftRouterPipeline extends AbstractHandlerBehaviour implements Pipe ...@@ -336,6 +338,16 @@ public class SoftRouterPipeline extends AbstractHandlerBehaviour implements Pipe
336 + "nextId or Treatment", fwd.selector(), fwd.appId()); 338 + "nextId or Treatment", fwd.selector(), fwd.appId());
337 return Collections.emptySet(); 339 return Collections.emptySet();
338 } 340 }
341 +
342 + int tableId = FILTER_TABLE;
343 +
344 + // A punt rule for IP traffic should be directed to the FIB table
345 + // so that it only takes effect if the packet misses the FIB rules
346 + if (fwd.treatment() != null && containsPunt(fwd.treatment()) &&
347 + fwd.selector() != null && matchesIp(fwd.selector())) {
348 + tableId = FIB_TABLE;
349 + }
350 +
339 TrafficTreatment.Builder ttBuilder = DefaultTrafficTreatment.builder(); 351 TrafficTreatment.Builder ttBuilder = DefaultTrafficTreatment.builder();
340 if (fwd.treatment() != null) { 352 if (fwd.treatment() != null) {
341 fwd.treatment().immediate().forEach(ins -> ttBuilder.add(ins)); 353 fwd.treatment().immediate().forEach(ins -> ttBuilder.add(ins));
...@@ -363,6 +375,7 @@ public class SoftRouterPipeline extends AbstractHandlerBehaviour implements Pipe ...@@ -363,6 +375,7 @@ public class SoftRouterPipeline extends AbstractHandlerBehaviour implements Pipe
363 FlowRule rule = DefaultFlowRule.builder() 375 FlowRule rule = DefaultFlowRule.builder()
364 .withSelector(fwd.selector()) 376 .withSelector(fwd.selector())
365 .withTreatment(ttBuilder.build()) 377 .withTreatment(ttBuilder.build())
378 + .forTable(tableId)
366 .makePermanent() 379 .makePermanent()
367 .forDevice(deviceId) 380 .forDevice(deviceId)
368 .fromApp(fwd.appId()) 381 .fromApp(fwd.appId())
...@@ -374,6 +387,17 @@ public class SoftRouterPipeline extends AbstractHandlerBehaviour implements Pipe ...@@ -374,6 +387,17 @@ public class SoftRouterPipeline extends AbstractHandlerBehaviour implements Pipe
374 return flowrules; 387 return flowrules;
375 } 388 }
376 389
390 + private boolean containsPunt(TrafficTreatment treatment) {
391 + return treatment.immediate().stream()
392 + .anyMatch(i -> i.type().equals(Instruction.Type.OUTPUT)
393 + && ((OutputInstruction) i).port().equals(PortNumber.CONTROLLER));
394 + }
395 +
396 + private boolean matchesIp(TrafficSelector selector) {
397 + EthTypeCriterion c = (EthTypeCriterion) selector.getCriterion(Criterion.Type.ETH_TYPE);
398 + return c != null && c.ethType().equals(EthType.EtherType.IPV4.ethType());
399 + }
400 +
377 /** 401 /**
378 * SoftRouter has a single specific table - the FIB Table. It emulates 402 * SoftRouter has a single specific table - the FIB Table. It emulates
379 * LPM matching of dstIP by using higher priority flows for longer prefixes. 403 * LPM matching of dstIP by using higher priority flows for longer prefixes.
......