blob: cfe9d11649bec58a04f2b3b3ea9998c80716e654 [file] [log] [blame]
/*
* Copyright 2016-present Open Networking Laboratory
*
* 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.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Deactivate;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.ReferenceCardinality;
import org.apache.felix.scr.annotations.Service;
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.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, enabled = true)
@Service
public class CastorArpManager implements ArpService {
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected ConnectivityManagerService connectivityManager;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected PacketService packetService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
protected CoreService coreService;
@Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
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() {
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;
}
}
}
}