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