| /* |
| * 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.sdxl2; |
| |
| |
| import org.onlab.packet.ARP; |
| import org.onlab.packet.Ethernet; |
| import org.onlab.packet.IpAddress; |
| import org.onlab.packet.Ip4Address; |
| import org.onlab.packet.Ip6Address; |
| import org.onlab.packet.VlanId; |
| import org.onlab.packet.IPv6; |
| import org.onlab.packet.ICMP6; |
| import org.onlab.packet.MacAddress; |
| import org.onlab.packet.ndp.NeighborSolicitation; |
| import org.onosproject.core.ApplicationId; |
| import org.onosproject.net.ConnectPoint; |
| 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.flow.criteria.Criterion; |
| import org.onosproject.net.flow.criteria.Criterion.Type; |
| import org.onosproject.net.flow.instructions.Instruction; |
| import org.onosproject.net.flow.instructions.L2ModificationInstruction; |
| import org.onosproject.net.intent.Intent; |
| import org.onosproject.net.intent.IntentService; |
| import org.onosproject.net.intent.IntentState; |
| import org.onosproject.net.intent.PointToPointIntent; |
| import org.onosproject.net.packet.DefaultOutboundPacket; |
| import org.onosproject.net.packet.InboundPacket; |
| import org.onosproject.net.packet.PacketContext; |
| import org.onosproject.net.packet.PacketService; |
| import org.slf4j.Logger; |
| |
| import java.nio.ByteBuffer; |
| import java.util.Iterator; |
| import java.util.Set; |
| |
| import static com.google.common.base.Preconditions.checkNotNull; |
| import static org.onosproject.net.flow.instructions.L2ModificationInstruction.ModVlanIdInstruction; |
| import static org.onosproject.security.AppGuard.checkPermission; |
| import static org.onosproject.security.AppPermission.Type.PACKET_WRITE; |
| import static org.slf4j.LoggerFactory.getLogger; |
| |
| /** |
| * Implementation of ARP and NDP handler based on ProxyArpManager. |
| */ |
| public class SdxL2ArpNdpHandler { |
| |
| private final Logger log = getLogger(SdxL2ArpNdpHandler.class); |
| |
| private static final String MSG_NULL = "ARP or NDP message cannot be null."; |
| |
| protected IntentService intentService; |
| |
| protected ApplicationId applicationId; |
| |
| protected PacketService packetService; |
| |
| public static String vcType; |
| |
| |
| public SdxL2ArpNdpHandler(IntentService intentService, |
| PacketService packetService, ApplicationId applicationId) { |
| this.intentService = intentService; |
| this.packetService = packetService; |
| this.applicationId = applicationId; |
| } |
| |
| /** |
| * Handles the ARP/NDP packets. |
| * |
| * @param context containing the packet to process. |
| * @return true if the packet has been handled otherwise false. |
| */ |
| public boolean handlePacket(PacketContext context) { |
| |
| checkPermission(PACKET_WRITE); |
| |
| 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: |
| return relay(msgContext); |
| case REQUEST: |
| return relay(msgContext); |
| default: |
| } |
| |
| return false; |
| } |
| |
| /** |
| * Relays the packets edge-to-edge |
| * applying the egress actions. |
| * |
| * @param ctx containing the ARP/NDP packet. |
| * @return true if the packet has been handled otherwise false. |
| */ |
| private boolean relay(MessageContext ctx) { |
| |
| Iterator<Intent> intents = intentService.getIntents().iterator(); |
| boolean resolved = false; |
| Intent intent; |
| while (intents.hasNext()) { |
| |
| intent = intents.next(); |
| if (intent.appId().equals(applicationId) && |
| intentService.getIntentState(intent.key()) == IntentState.INSTALLED) { |
| |
| PointToPointIntent ppIntent = (PointToPointIntent) intent; |
| TrafficSelector selector = ctx.selector(); |
| |
| if (ctx.inPort().equals(ppIntent.ingressPoint()) && |
| partialequals(selector, ppIntent.selector())) { |
| |
| Ethernet packet = ctx.packet(); |
| |
| checkNotNull(packet, MSG_NULL); |
| |
| applytreatment(packet, ppIntent.treatment()); |
| |
| TrafficTreatment.Builder builder; |
| builder = DefaultTrafficTreatment.builder(); |
| builder.setOutput(ppIntent.egressPoint().port()); |
| TrafficTreatment treatment = builder.build(); |
| |
| |
| ByteBuffer buf = ByteBuffer.wrap(packet.serialize()); |
| packetService.emit(new DefaultOutboundPacket(ppIntent.egressPoint().deviceId(), |
| treatment, buf)); |
| resolved = true; |
| break; |
| |
| } |
| } |
| } |
| |
| return resolved; |
| } |
| |
| /** |
| * Verifies if the TrafficSelector of the packet is equal |
| * to the one derived from an SDX-L2 Intent. |
| * |
| * @param generated the traffic selector of the packet. |
| * @param fromIntent the traffic selector of the intent. |
| * @return true if all the criteria match. |
| */ |
| private boolean partialequals(TrafficSelector generated, TrafficSelector fromIntent) { |
| |
| Set<Criterion> criteria = generated.criteria(); |
| for (Criterion criterion : criteria) { |
| if (!vcType.equals("MAC") && criterion.type().equals(Type.ETH_SRC)) { |
| continue; |
| } |
| |
| if (!fromIntent.criteria().contains(criterion)) { |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| /** |
| * Applies the egress TrafficTreatment we found |
| * in the associated SDX-L2 Intent. |
| * |
| * @param packet the packet to modify. |
| * @param treatment the TrafficTreatment to apply. |
| */ |
| private void applytreatment(Ethernet packet, TrafficTreatment treatment) { |
| |
| for (Instruction instruction : treatment.allInstructions()) { |
| if (instruction.type() == Instruction.Type.L2MODIFICATION) { |
| L2ModificationInstruction l2instruction = (L2ModificationInstruction) instruction; |
| switch (l2instruction.subtype()) { |
| case VLAN_ID: |
| ModVlanIdInstruction modVlanIdInstruction = (ModVlanIdInstruction) l2instruction; |
| packet.setVlanID(modVlanIdInstruction.vlanId().toShort()); |
| break; |
| case VLAN_PUSH: |
| packet.setVlanID((short) 1); |
| break; |
| case VLAN_POP: |
| packet.setVlanID(Ethernet.VLAN_UNTAGGED); |
| break; |
| default: |
| } |
| } |
| } |
| |
| } |
| |
| /** |
| * Attempts to create a MessageContext for the given Ethernet frame. If the |
| * frame is a valid ARP or NDP request or response, a context will be |
| * created. |
| * |
| * @param eth input Ethernet frame |
| * @param inPort in port |
| * @return MessageContext if the packet was ARP or NDP, otherwise null |
| */ |
| private MessageContext createContext(Ethernet eth, ConnectPoint inPort) { |
| if (eth.getEtherType() == Ethernet.TYPE_ARP) { |
| return createArpContext(eth, inPort); |
| } else if (eth.getEtherType() == Ethernet.TYPE_IPV6) { |
| return createNdpContext(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); |
| } |
| |
| /** |
| * Extracts context information from NDP packets. |
| * |
| * @param eth input Ethernet frame that is thought to be NDP |
| * @param inPort in port |
| * @return MessageContext object if the packet was a valid NDP packet, |
| * otherwise null |
| */ |
| private MessageContext createNdpContext(Ethernet eth, ConnectPoint inPort) { |
| if (eth.getEtherType() != Ethernet.TYPE_IPV6) { |
| return null; |
| } |
| IPv6 ipv6 = (IPv6) eth.getPayload(); |
| |
| if (ipv6.getNextHeader() != IPv6.PROTOCOL_ICMP6) { |
| return null; |
| } |
| ICMP6 icmpv6 = (ICMP6) ipv6.getPayload(); |
| |
| IpAddress sender = Ip6Address.valueOf(ipv6.getSourceAddress()); |
| IpAddress target = null; |
| |
| MessageType type; |
| if (icmpv6.getIcmpType() == ICMP6.NEIGHBOR_SOLICITATION) { |
| type = MessageType.REQUEST; |
| NeighborSolicitation nsol = (NeighborSolicitation) icmpv6.getPayload(); |
| target = Ip6Address.valueOf(nsol.getTargetAddress()); |
| } else if (icmpv6.getIcmpType() == ICMP6.NEIGHBOR_ADVERTISEMENT) { |
| type = MessageType.REPLY; |
| } else { |
| return null; |
| } |
| |
| return new MessageContext(eth, inPort, Protocol.NDP, type, target, sender); |
| } |
| |
| |
| |
| private enum Protocol { |
| ARP, NDP |
| } |
| |
| private enum MessageType { |
| REQUEST, REPLY |
| } |
| |
| /** |
| * Provides context information for a particular ARP or NDP message, with |
| * a unified interface to access data regardless of protocol. |
| */ |
| 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; |
| } |
| |
| /** |
| * Builds TrafficSelector using data |
| * in MessageContext. |
| * |
| * @return the TrafficSelector object |
| */ |
| public TrafficSelector selector() { |
| switch (vcType) { |
| case "MPLS": |
| case "VLAN": |
| default: |
| } |
| |
| return buildMacSelector(this.srcMac(), this.vlan()); |
| |
| } |
| |
| /** |
| * /** |
| * Builds TrafficSelector for MAC |
| * based tunnels. |
| * |
| * @param srcMac the sender's MAC address. |
| * @param ingressTag the VLAN tag at ingress. |
| * @return the TrafficSelector object |
| */ |
| private TrafficSelector buildMacSelector(MacAddress srcMac, VlanId ingressTag) { |
| |
| TrafficSelector.Builder selectorBuilder = DefaultTrafficSelector.builder(); |
| selectorBuilder.matchEthSrc(srcMac); |
| if (ingressTag.toShort() != VlanId.UNTAGGED) { |
| selectorBuilder.matchVlanId(ingressTag); |
| } |
| return selectorBuilder.build(); |
| } |
| |
| } |
| |
| } |