| /* |
| * Copyright 2016-present Open Networking Foundation |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package org.onosproject.castor; |
| |
| import org.onlab.packet.ARP; |
| import org.onlab.packet.Ethernet; |
| import org.onlab.packet.Ip4Address; |
| import org.onlab.packet.IpAddress; |
| import org.onlab.packet.MacAddress; |
| import org.onlab.packet.VlanId; |
| import org.onosproject.core.ApplicationId; |
| import org.onosproject.core.CoreService; |
| import org.onosproject.net.ConnectPoint; |
| import org.onosproject.net.DeviceId; |
| import org.onosproject.net.flow.DefaultTrafficSelector; |
| import org.onosproject.net.flow.DefaultTrafficTreatment; |
| import org.onosproject.net.flow.TrafficSelector; |
| import org.onosproject.net.flow.TrafficTreatment; |
| import org.onosproject.net.packet.DefaultOutboundPacket; |
| import org.onosproject.net.packet.InboundPacket; |
| import org.onosproject.net.packet.PacketContext; |
| import org.onosproject.net.packet.PacketProcessor; |
| import org.onosproject.net.packet.PacketService; |
| import org.osgi.service.component.annotations.Activate; |
| import org.osgi.service.component.annotations.Component; |
| import org.osgi.service.component.annotations.Deactivate; |
| import org.osgi.service.component.annotations.Reference; |
| import org.osgi.service.component.annotations.ReferenceCardinality; |
| import org.slf4j.Logger; |
| |
| import java.nio.ByteBuffer; |
| import java.util.Optional; |
| import java.util.Set; |
| |
| import static org.onlab.packet.Ethernet.TYPE_ARP; |
| import static org.onosproject.net.packet.PacketPriority.CONTROL; |
| import static org.slf4j.LoggerFactory.getLogger; |
| |
| /** |
| * Component for managing the ARPs. |
| */ |
| |
| @Component(immediate = true, service = ArpService.class) |
| public class CastorArpManager implements ArpService { |
| |
| @Reference(cardinality = ReferenceCardinality.MANDATORY) |
| protected ConnectivityManagerService connectivityManager; |
| |
| @Reference(cardinality = ReferenceCardinality.MANDATORY) |
| protected PacketService packetService; |
| |
| @Reference(cardinality = ReferenceCardinality.MANDATORY) |
| protected CoreService coreService; |
| |
| @Reference(cardinality = ReferenceCardinality.MANDATORY) |
| protected CastorStore castorStore; |
| |
| private ProxyArpProcessor processor = new ProxyArpProcessor(); |
| |
| private final Logger log = getLogger(getClass()); |
| private static final int FLOW_PRIORITY = 500; |
| private static final MacAddress ARP_SOURCEMAC = MacAddress.valueOf("00:00:00:00:00:01"); |
| private static final MacAddress ARP_DEST = MacAddress.valueOf("00:00:00:00:00:00"); |
| private static final byte[] ZERO_MAC_ADDRESS = MacAddress.ZERO.toBytes(); |
| private static final IpAddress ARP_SRC = Ip4Address.valueOf("0.0.0.0"); |
| |
| private ApplicationId appId; |
| Optional<DeviceId> deviceID = null; |
| |
| private enum Protocol { |
| ARP |
| } |
| |
| private enum MessageType { |
| REQUEST, REPLY |
| } |
| |
| @Activate |
| public void activate() { |
| appId = coreService.getAppId(Castor.CASTOR_APP); |
| packetService.addProcessor(processor, PacketProcessor.director(1)); |
| requestPackets(); |
| } |
| |
| @Deactivate |
| public void deactivate() { |
| withdrawIntercepts(); |
| packetService.removeProcessor(processor); |
| processor = null; |
| } |
| |
| /** |
| * Used to request the ARP packets. |
| */ |
| private void requestPackets() { |
| TrafficSelector.Builder selectorBuilder = |
| DefaultTrafficSelector.builder(); |
| selectorBuilder.matchEthType(TYPE_ARP); |
| packetService.requestPackets(selectorBuilder.build(), CONTROL, appId); |
| } |
| |
| /** |
| * Withdraws the requested ARP packets. |
| */ |
| private void withdrawIntercepts() { |
| if (deviceID != null && deviceID.isPresent()) { |
| TrafficSelector.Builder selectorBuilder = |
| DefaultTrafficSelector.builder(); |
| selectorBuilder.matchEthType(TYPE_ARP); |
| packetService.cancelPackets(selectorBuilder.build(), CONTROL, appId, deviceID); |
| } |
| } |
| |
| /** |
| * Forwards the ARP packet to the specified connect point via packet out. |
| * |
| * @param context The packet context |
| */ |
| private void forward(MessageContext context) { |
| |
| TrafficTreatment.Builder builder = null; |
| Ethernet eth = context.packet(); |
| ByteBuffer buf = ByteBuffer.wrap(eth.serialize()); |
| |
| IpAddress target = context.target(); |
| String value = getMatchingConnectPoint(target); |
| if (value != null) { |
| ConnectPoint connectPoint = ConnectPoint.deviceConnectPoint(value); |
| builder = DefaultTrafficTreatment.builder(); |
| builder.setOutput(connectPoint.port()); |
| packetService.emit(new DefaultOutboundPacket(connectPoint.deviceId(), builder.build(), buf)); |
| } |
| } |
| |
| @Override |
| public void createArp(Peer peer) { |
| |
| Ethernet packet = null; |
| packet = buildArpRequest(peer); |
| ByteBuffer buf = ByteBuffer.wrap(packet.serialize()); |
| ConnectPoint connectPoint = ConnectPoint.deviceConnectPoint(peer.getPort()); |
| |
| TrafficTreatment.Builder builder = DefaultTrafficTreatment.builder(); |
| builder.setOutput(connectPoint.port()); |
| packetService.emit(new DefaultOutboundPacket(connectPoint.deviceId(), builder.build(), buf)); |
| |
| } |
| |
| /** |
| * Builds the ARP request when MAC is not known. |
| * |
| * @param peer The Peer whose MAC is not known. |
| * @return Ethernet |
| */ |
| private Ethernet buildArpRequest(Peer peer) { |
| ARP arp = new ARP(); |
| arp.setHardwareType(ARP.HW_TYPE_ETHERNET) |
| .setHardwareAddressLength((byte) Ethernet.DATALAYER_ADDRESS_LENGTH) |
| .setProtocolType(ARP.PROTO_TYPE_IP) |
| .setProtocolAddressLength((byte) IpAddress.INET_BYTE_LENGTH) |
| .setOpCode(ARP.OP_REQUEST); |
| |
| arp.setSenderHardwareAddress(ARP_SOURCEMAC.toBytes()) |
| .setSenderProtocolAddress(ARP_SRC.toOctets()) |
| .setTargetHardwareAddress(ZERO_MAC_ADDRESS) |
| .setTargetProtocolAddress(IpAddress.valueOf(peer.getIpAddress()).toOctets()); |
| |
| Ethernet ethernet = new Ethernet(); |
| ethernet.setEtherType(Ethernet.TYPE_ARP) |
| .setDestinationMACAddress(MacAddress.BROADCAST) |
| .setSourceMACAddress(ARP_SOURCEMAC) |
| .setPayload(arp); |
| ethernet.setPad(true); |
| |
| return ethernet; |
| } |
| |
| /** |
| * Gets the matching connect point corresponding to the peering IP address. |
| * |
| * @param target Target IP address |
| * @return Connect point as a String |
| */ |
| private String getMatchingConnectPoint(IpAddress target) { |
| Set<Peer> peers = castorStore.getAllPeers(); |
| for (Peer peer : peers) { |
| IpAddress match = IpAddress.valueOf(peer.getIpAddress()); |
| if (match.equals(target)) { |
| return peer.getPort(); |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Returns the matching Peer or route server on a Connect Point. |
| * |
| * @param connectPoint The peering connect point. |
| * @return Peer or Route Server |
| */ |
| private Peer getMatchingPeer(ConnectPoint connectPoint) { |
| |
| for (Peer peer : castorStore.getAllPeers()) { |
| if (connectPoint.equals(ConnectPoint.deviceConnectPoint(peer.getPort()))) { |
| return peer; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Returns matching BGP Peer on a connect point. |
| * |
| * @param connectPoint The peering connect point. |
| * @return The Peer |
| */ |
| private Peer getMatchingCustomer(ConnectPoint connectPoint) { |
| |
| for (Peer peer : castorStore.getCustomers()) { |
| if (connectPoint.equals(ConnectPoint.deviceConnectPoint(peer.getPort()))) { |
| return peer; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Updates the IP address to mac address map. |
| * |
| * @param context The message context. |
| */ |
| private void updateMac(MessageContext context) { |
| |
| if ((castorStore.getAddressMap()).containsKey(context.sender())) { |
| return; |
| } |
| Ethernet eth = context.packet(); |
| MacAddress macAddress = eth.getSourceMAC(); |
| IpAddress ipAddress = context.sender(); |
| castorStore.setAddressMap(ipAddress, macAddress); |
| } |
| |
| /** |
| * Setup the layer two flows if not already installed after an ARP packet is received. |
| * If the layer 2 status is true, means layer two flows are already provisioned. |
| * If the status was false, layer 2 flows will be installed at this point. This |
| * happens when the mac address of a peer was not known at the time of its addition. |
| * |
| * @param msgContext The message context. |
| */ |
| private void handleArpForL2(MessageContext msgContext) { |
| |
| ConnectPoint cp = msgContext.inPort(); |
| Peer peer = getMatchingCustomer(cp); |
| |
| if (peer != null && !peer.getl2Status()) { |
| connectivityManager.setUpL2(peer); |
| } |
| } |
| |
| @Override |
| public boolean handlePacket(PacketContext context) { |
| |
| InboundPacket pkt = context.inPacket(); |
| Ethernet ethPkt = pkt.parsed(); |
| |
| if (ethPkt == null) { |
| return false; |
| } |
| |
| MessageContext msgContext = createContext(ethPkt, pkt.receivedFrom()); |
| |
| if (msgContext == null) { |
| return false; |
| } |
| switch (msgContext.type()) { |
| case REPLY: |
| forward(msgContext); |
| updateMac(msgContext); |
| handleArpForL2(msgContext); |
| break; |
| case REQUEST: |
| forward(msgContext); |
| updateMac(msgContext); |
| handleArpForL2(msgContext); |
| break; |
| default: |
| return false; |
| } |
| context.block(); |
| return true; |
| } |
| |
| private MessageContext createContext(Ethernet eth, ConnectPoint inPort) { |
| if (eth.getEtherType() == Ethernet.TYPE_ARP) { |
| return createArpContext(eth, inPort); |
| } |
| return null; |
| } |
| |
| /** |
| * Extracts context information from ARP packets. |
| * |
| * @param eth input Ethernet frame that is thought to be ARP |
| * @param inPort in port |
| * @return MessageContext object if the packet was a valid ARP packet, |
| * otherwise null |
| */ |
| private MessageContext createArpContext(Ethernet eth, ConnectPoint inPort) { |
| if (eth.getEtherType() != Ethernet.TYPE_ARP) { |
| return null; |
| } |
| |
| ARP arp = (ARP) eth.getPayload(); |
| |
| IpAddress target = Ip4Address.valueOf(arp.getTargetProtocolAddress()); |
| IpAddress sender = Ip4Address.valueOf(arp.getSenderProtocolAddress()); |
| |
| MessageType type; |
| if (arp.getOpCode() == ARP.OP_REQUEST) { |
| type = MessageType.REQUEST; |
| } else if (arp.getOpCode() == ARP.OP_REPLY) { |
| type = MessageType.REPLY; |
| } else { |
| return null; |
| } |
| return new MessageContext(eth, inPort, Protocol.ARP, type, target, sender); |
| } |
| |
| private class MessageContext { |
| private Protocol protocol; |
| private MessageType type; |
| |
| private IpAddress target; |
| private IpAddress sender; |
| |
| private Ethernet eth; |
| private ConnectPoint inPort; |
| |
| public MessageContext(Ethernet eth, ConnectPoint inPort, |
| Protocol protocol, MessageType type, |
| IpAddress target, IpAddress sender) { |
| this.eth = eth; |
| this.inPort = inPort; |
| this.protocol = protocol; |
| this.type = type; |
| this.target = target; |
| this.sender = sender; |
| } |
| |
| public ConnectPoint inPort() { |
| return inPort; |
| } |
| |
| public Ethernet packet() { |
| return eth; |
| } |
| |
| public Protocol protocol() { |
| return protocol; |
| } |
| |
| public MessageType type() { |
| return type; |
| } |
| |
| public VlanId vlan() { |
| return VlanId.vlanId(eth.getVlanID()); |
| } |
| |
| public MacAddress srcMac() { |
| return MacAddress.valueOf(eth.getSourceMACAddress()); |
| } |
| |
| public IpAddress target() { |
| return target; |
| } |
| |
| public IpAddress sender() { |
| return sender; |
| } |
| } |
| private class ProxyArpProcessor implements PacketProcessor { |
| |
| @Override |
| public void process(PacketContext context) { |
| |
| if (context.isHandled()) { |
| return; |
| } |
| InboundPacket pkt = context.inPacket(); |
| Ethernet ethPkt = pkt.parsed(); |
| if (ethPkt == null) { |
| return; |
| } |
| if (ethPkt.getEtherType() == TYPE_ARP) { |
| //handle the arp packet. |
| handlePacket(context); |
| } else { |
| return; |
| } |
| } |
| } |
| } |