| /* |
| * Copyright 2015-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.fnl.base; |
| |
| import org.onosproject.fnl.intf.NetworkAnomaly; |
| import org.onosproject.net.DeviceId; |
| import org.onosproject.net.Link; |
| import org.onosproject.net.flow.FlowEntry; |
| import org.onosproject.net.flow.criteria.Criterion; |
| import org.onosproject.net.flow.criteria.EthCriterion; |
| import org.onosproject.net.flow.criteria.EthTypeCriterion; |
| import org.onosproject.net.flow.criteria.IPCriterion; |
| import org.onosproject.net.flow.criteria.IPDscpCriterion; |
| import org.onosproject.net.flow.criteria.IPEcnCriterion; |
| import org.onosproject.net.flow.criteria.IPProtocolCriterion; |
| import org.onosproject.net.flow.criteria.PortCriterion; |
| import org.onosproject.net.flow.criteria.TcpPortCriterion; |
| import org.onosproject.net.flow.criteria.UdpPortCriterion; |
| import org.onosproject.net.flow.criteria.VlanIdCriterion; |
| import org.onosproject.net.flow.criteria.VlanPcpCriterion; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Stack; |
| import java.util.ArrayList; |
| import java.util.Iterator; |
| import java.util.Collections; |
| |
| import static org.onosproject.fnl.intf.NetworkAnomaly.Type.LOOP; |
| import static org.onosproject.fnl.base.TsLoopPacket.SetHeaderResult.SETHEADER_FAILURE_NULL; |
| import static org.onosproject.fnl.base.TsLoopPacket.SetHeaderResult.SETHEADER_OVERRIDE; |
| import static org.onosproject.fnl.base.TsLoopPacket.SetHeaderResult.SETHEADER_SUCCESS; |
| import static org.onosproject.net.flow.criteria.Criteria.*; |
| |
| /** |
| * Virtual packet for Default Loop Checking. |
| */ |
| public final class TsLoopPacket implements NetworkAnomaly { |
| |
| private final Logger log = LoggerFactory.getLogger(getClass()); |
| |
| private static final String EOL = String.format("%n"); |
| private static final String LINE = |
| EOL + "====================================================" + EOL; |
| private static final String HDR_FMT = EOL + "---------- %s ----------" + EOL; |
| private static final String LOOP_HEADER = makeHeader("Loop Header"); |
| private static final String LOOP_FLOW_ENTRIES = makeHeader("Loop Flow Entries"); |
| private static final String LOOP_LINKS = makeHeader("Loop Links"); |
| |
| private Map<Criterion.Type, Criterion> match; |
| private Stack<FlowEntry> pathFlow; |
| // when Upgrade, check to MAKE SURE it include just Link but not EdgeLink |
| private Stack<Link> pathLink; |
| |
| /** |
| * Create an initial virtual packet inside for Loop Checking. |
| */ |
| private TsLoopPacket() { |
| match = new HashMap<>(); |
| pathFlow = new Stack<>(); |
| pathLink = new Stack<>(); |
| } |
| |
| @Override |
| public Type type() { |
| return LOOP; |
| } |
| |
| /** |
| * Represents the result of setting a header to virtual packet. |
| */ |
| public enum SetHeaderResult { |
| /** |
| * Set header successfully. |
| */ |
| SETHEADER_SUCCESS, |
| |
| /** |
| * Set header successfully but override old value. |
| */ |
| SETHEADER_OVERRIDE, |
| |
| /** |
| * Fail to set Header because NULL value. |
| */ |
| SETHEADER_FAILURE_NULL, |
| |
| /** |
| * Fail to set Header, but reason is not defined, defined in advance. |
| */ |
| SETHEADER_FAILURE |
| } |
| |
| /** |
| * Creates and returns a new packet instance with the copied match fields. |
| * |
| * With hard-copied match fields, references to path flows and path links. |
| * |
| * @return new loop packet instance with the copied match fields |
| */ |
| public TsLoopPacket copyPacketMatch() { |
| |
| TsLoopPacket newOne = new TsLoopPacket(); |
| |
| newOne.pathFlow = this.pathFlow; |
| newOne.pathLink = this.pathLink; |
| |
| Map<Criterion.Type, Criterion> m = newOne.match; |
| |
| for (Map.Entry<Criterion.Type, Criterion> entry : this.match.entrySet()) { |
| Criterion.Type k = entry.getKey(); |
| Criterion v = entry.getValue(); |
| |
| switch (k) { |
| case IN_PORT: |
| m.put(k, matchInPort(((PortCriterion) v).port())); |
| break; |
| case ETH_SRC: // At present, not support Ethernet mask (ONOS?) |
| m.put(k, matchEthSrc(((EthCriterion) v).mac())); |
| break; |
| case ETH_DST: // At present, not support Ethernet mask (ONOS?) |
| m.put(k, matchEthDst(((EthCriterion) v).mac())); |
| break; |
| case ETH_TYPE: |
| m.put(k, matchEthType(((EthTypeCriterion) v).ethType())); |
| break; |
| case VLAN_VID: // At present, not support VLAN mask (ONOS?) |
| m.put(k, matchVlanId(((VlanIdCriterion) v).vlanId())); |
| break; |
| case VLAN_PCP: |
| m.put(k, matchVlanPcp(((VlanPcpCriterion) v).priority())); |
| break; |
| case IPV4_SRC: |
| m.put(k, matchIPSrc(((IPCriterion) v).ip())); |
| break; |
| case IPV4_DST: |
| m.put(k, matchIPDst(((IPCriterion) v).ip())); |
| break; |
| case IP_PROTO: |
| m.put(k, matchIPProtocol(((IPProtocolCriterion) v).protocol())); |
| break; |
| case IP_DSCP: // can't be supported by now |
| m.put(k, matchIPDscp(((IPDscpCriterion) v).ipDscp())); |
| break; |
| case IP_ECN: // can't be supported by now |
| m.put(k, matchIPEcn(((IPEcnCriterion) v).ipEcn())); |
| break; |
| case TCP_SRC: |
| m.put(k, matchTcpSrc(((TcpPortCriterion) v).tcpPort())); |
| break; |
| case TCP_DST: |
| m.put(k, matchTcpDst(((TcpPortCriterion) v).tcpPort())); |
| break; |
| case UDP_SRC: |
| m.put(k, matchUdpSrc(((UdpPortCriterion) v).udpPort())); |
| break; |
| case UDP_DST: |
| m.put(k, matchUdpDst(((UdpPortCriterion) v).udpPort())); |
| break; |
| default: //can't be supported by OF1.0 |
| log.debug("{} can't be supported by OF1.0", k); |
| break; |
| } |
| } |
| return newOne; |
| } |
| |
| /** |
| * Sets the given criterion as a packet header field. |
| * |
| * @param criterion as packet header field |
| * @return the result of set action |
| */ |
| public SetHeaderResult setHeader(Criterion criterion) { |
| |
| if (criterion == null) { |
| return SETHEADER_FAILURE_NULL; |
| } |
| |
| boolean hasKey = match.containsKey(criterion.type()); |
| |
| match.put(criterion.type(), criterion); |
| |
| return hasKey ? SETHEADER_OVERRIDE : SETHEADER_SUCCESS; |
| } |
| |
| /** |
| * Deletes a packet header field by the designated header type. |
| * |
| * @param criterionType as packet header type |
| * @return true, if packet contained the corresponding type of header; |
| * false, otherwise |
| */ |
| public boolean delHeader(Criterion.Type criterionType) { |
| return match.remove(criterionType) != null; |
| } |
| |
| /** |
| * Returns a packet header field value by the designated header type. |
| * |
| * Returns null if the field does not exist. |
| * |
| * @param criterionType as packet header type |
| * @return the packet header field value; may be null |
| */ |
| public Criterion getHeader(Criterion.Type criterionType) { |
| return match.get(criterionType); |
| } |
| |
| /** |
| * Returns true if there is the type of header field in the packet. |
| * |
| * @param criterionType packet header type |
| * @return true if the field exists; false otherwise |
| */ |
| public boolean headerExists(Criterion.Type criterionType) { |
| return match.containsKey(criterionType); |
| } |
| |
| /** |
| * Pushes the given flow entry onto the path flow stack. |
| * Packet matches this entry in specific switch hop. |
| * |
| * @param entry the matched entry |
| */ |
| public void pushPathFlow(FlowEntry entry) { |
| pathFlow.push(entry); |
| } |
| |
| /** |
| * Pops a FlowEntry from path flow entry stack. |
| */ |
| public void popPathFlow() { |
| pathFlow.pop(); |
| } |
| |
| /** |
| * Returns links in the path which the packet passes through. |
| * |
| * @return an iterator over the set of links |
| */ |
| public Iterator<Link> getPathLink() { |
| return pathLink.iterator(); |
| } |
| |
| /** |
| * Adds a Link to path link list. |
| * Packet goes through this link between two switches. |
| * |
| * @param link The link through which the packet go |
| */ |
| public void pushPathLink(Link link) { |
| // TODO - need CPY link manual? |
| pathLink.push(link); |
| } |
| |
| /** |
| * Removes a Link from path link list. |
| */ |
| public void popPathLink() { |
| pathLink.pop(); |
| } |
| |
| /** |
| * Returns true if the packet passed through the specific device. |
| * |
| * @param deviceId identify of the divice to test |
| * @return true if packet passed through the specific device; |
| * false otherwise |
| */ |
| public boolean isPassedDevice(DeviceId deviceId) { |
| for (Link linkTemp : pathLink) { |
| if (deviceId.equals(linkTemp.src().deviceId())) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Returns the IN_PORT header field of the packet. |
| * |
| * Attention: |
| * IN_PORT field will be changed when packet goes into the next switch hop. |
| * |
| * @return a port criterion object. |
| */ |
| public PortCriterion getInport() { |
| // TODO - check IN_PORT or IN_PHY_PORT |
| return (PortCriterion) match.get(Criterion.Type.IN_PORT); |
| } |
| |
| /** |
| * Creates and returns a loop packet instance with given Match Fields. |
| * |
| * Returns null, |
| * whenever SetHeader_FAILURE or SETHEADER_FAILURE_NULL happened. |
| * |
| * @param criteria match field of one flow entry |
| * @param collision as return value; |
| * true, if criteria contain multiple ones with same type |
| * @return a new loop packet instance; may be null |
| */ |
| public static TsLoopPacket matchBuilder(Iterable<Criterion> criteria, |
| TsReturn<Boolean> collision) { |
| |
| if (null != collision) { |
| collision.setValue(false); |
| } |
| |
| TsLoopPacket pkt = new TsLoopPacket(); |
| |
| for (Criterion criterion : criteria) { |
| |
| SetHeaderResult ret = pkt.setHeader(criterion); |
| |
| if (SETHEADER_SUCCESS == ret) { |
| //TODO - in the future, we may need to resolve this condition |
| } else if (SETHEADER_OVERRIDE == ret) { |
| if (null != collision) { |
| collision.setValue(true); |
| } |
| } else { // SetHeader_FAILURE or SetHeader_FAILURE_NULL |
| pkt = null; |
| break; |
| } |
| } |
| |
| return pkt; |
| } |
| |
| /** |
| * Hands in the header of virtual packet one by one. |
| * Let the header go up through every layer of recursion. |
| * It is called when a loop is discovered. |
| * |
| * @param loopPkt virtual packet that will trigger Loop Storm |
| */ |
| public void handInLoopMatch(TsLoopPacket loopPkt) { |
| match = loopPkt.match; |
| } |
| |
| /** |
| * Resets the path link and path flow structures. |
| * And initializing the path flow with the gicen flow entry. |
| * |
| * @param firstEntry the flow entry from which this packet is built |
| */ |
| public void resetLinkFlow(FlowEntry firstEntry) { |
| pathLink = new Stack<>(); |
| pathFlow = new Stack<>(); |
| pathFlow.push(firstEntry); |
| } |
| |
| private static String makeHeader(String title) { |
| return String.format(HDR_FMT, title); |
| } |
| |
| /** |
| * Returns a multi-line string representation of this loop packet instance. |
| * |
| * @return formatted string |
| */ |
| @Override |
| public String toString() { |
| StringBuilder me = new StringBuilder(); |
| |
| me.append(LINE); |
| |
| me.append(LOOP_HEADER); |
| |
| List<Criterion> criteria = new ArrayList<>(match.values()); |
| Collections.sort(criteria, (o1, o2) -> o1.type().compareTo(o2.type())); |
| |
| for (Criterion c : criteria) { |
| me.append(c).append(EOL); |
| } |
| |
| me.append(LOOP_FLOW_ENTRIES); |
| |
| for (FlowEntry flow : pathFlow) { |
| me.append(flow).append(EOL); |
| } |
| |
| me.append(LOOP_LINKS); |
| |
| for (Link l : pathLink) { |
| me.append(l).append(EOL); |
| } |
| |
| return me.toString(); |
| } |
| } |