Committed by
Gerrit Code Review
Add ability for vRouter to reactively send packets to directly connected hosts.
Change-Id: I652ad33acf95b5ef5806699135382d8be1260781
Showing
3 changed files
with
307 additions
and
0 deletions
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. | ... | ... |
-
Please register or login to post a comment