| /* |
| * Copyright 2015 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.onos.oneping; |
| |
| import com.google.common.collect.HashMultimap; |
| 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.onlab.packet.Ethernet; |
| import org.onlab.packet.IPv4; |
| import org.onlab.packet.MacAddress; |
| import org.onosproject.core.ApplicationId; |
| import org.onosproject.core.CoreService; |
| import org.onosproject.net.DeviceId; |
| import org.onosproject.net.flow.DefaultTrafficSelector; |
| import org.onosproject.net.flow.DefaultTrafficTreatment; |
| import org.onosproject.net.flow.FlowRule; |
| import org.onosproject.net.flow.FlowRuleEvent; |
| import org.onosproject.net.flow.FlowRuleListener; |
| import org.onosproject.net.flow.FlowRuleService; |
| 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.EthCriterion; |
| import org.onosproject.net.flowobjective.DefaultForwardingObjective; |
| import org.onosproject.net.flowobjective.FlowObjectiveService; |
| import org.onosproject.net.flowobjective.ForwardingObjective; |
| import org.onosproject.net.packet.PacketContext; |
| import org.onosproject.net.packet.PacketPriority; |
| import org.onosproject.net.packet.PacketProcessor; |
| import org.onosproject.net.packet.PacketService; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| import java.util.Objects; |
| import java.util.Optional; |
| import java.util.Timer; |
| import java.util.TimerTask; |
| |
| import static org.onosproject.net.flow.FlowRuleEvent.Type.RULE_REMOVED; |
| import static org.onosproject.net.flow.criteria.Criterion.Type.ETH_SRC; |
| |
| /** |
| * Sample application that permits only one ICMP ping per minute for a unique |
| * src/dst MAC pair per switch. |
| */ |
| @Component(immediate = true) |
| public class OnePing { |
| |
| private static Logger log = LoggerFactory.getLogger(OnePing.class); |
| |
| private static final String MSG_PINGED_ONCE = |
| "Thank you, Vasili. One ping from {} to {} received by {}"; |
| private static final String MSG_PINGED_TWICE = |
| "What are you doing, Vasili?! I said one ping only!!! " + |
| "Ping from {} to {} has already been received by {};" + |
| " 60 second ban has been issued"; |
| private static final String MSG_PING_REENABLED = |
| "Careful next time, Vasili! Re-enabled ping from {} to {} on {}"; |
| |
| private static final int PRIORITY = 128; |
| private static final int DROP_PRIORITY = 129; |
| private static final int TIMEOUT_SEC = 60; // seconds |
| |
| @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) |
| protected CoreService coreService; |
| |
| @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) |
| protected FlowObjectiveService flowObjectiveService; |
| |
| @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) |
| protected FlowRuleService flowRuleService; |
| |
| @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY) |
| protected PacketService packetService; |
| |
| private ApplicationId appId; |
| private final PacketProcessor packetProcessor = new PingPacketProcessor(); |
| private final FlowRuleListener flowListener = new InternalFlowListener(); |
| |
| // Selector for ICMP traffic that is to be intercepted |
| private final TrafficSelector intercept = DefaultTrafficSelector.builder() |
| .matchEthType(Ethernet.TYPE_IPV4).matchIPProtocol(IPv4.PROTOCOL_ICMP) |
| .build(); |
| |
| // Means to track detected pings from each device on a temporary basis |
| private final HashMultimap<DeviceId, PingRecord> pings = HashMultimap.create(); |
| private final Timer timer = new Timer("oneping-sweeper"); |
| |
| @Activate |
| public void activate() { |
| appId = coreService.registerApplication("org.onosproject.oneping", |
| () -> log.info("Periscope down.")); |
| packetService.addProcessor(packetProcessor, PRIORITY); |
| flowRuleService.addListener(flowListener); |
| packetService.requestPackets(intercept, PacketPriority.CONTROL, appId, |
| Optional.empty()); |
| log.info("Started"); |
| } |
| |
| @Deactivate |
| public void deactivate() { |
| packetService.removeProcessor(packetProcessor); |
| flowRuleService.removeFlowRulesById(appId); |
| flowRuleService.removeListener(flowListener); |
| log.info("Stopped"); |
| } |
| |
| // Processes the specified ICMP ping packet. |
| private void processPing(PacketContext context, Ethernet eth) { |
| DeviceId deviceId = context.inPacket().receivedFrom().deviceId(); |
| MacAddress src = eth.getSourceMAC(); |
| MacAddress dst = eth.getDestinationMAC(); |
| PingRecord ping = new PingRecord(src, dst); |
| boolean pinged = pings.get(deviceId).contains(ping); |
| |
| if (pinged) { |
| // Two pings detected; ban further pings and block packet-out |
| log.warn(MSG_PINGED_TWICE, src, dst, deviceId); |
| banPings(deviceId, src, dst); |
| context.block(); |
| } else { |
| // One ping detected; track it for the next minute |
| log.info(MSG_PINGED_ONCE, src, dst, deviceId); |
| pings.put(deviceId, ping); |
| timer.schedule(new PingPruner(deviceId, ping), TIMEOUT_SEC * 1000); |
| } |
| } |
| |
| // Installs a temporary drop rule for the ICMP pings between given srd/dst. |
| private void banPings(DeviceId deviceId, MacAddress src, MacAddress dst) { |
| TrafficSelector selector = DefaultTrafficSelector.builder() |
| .matchEthSrc(src).matchEthDst(dst).build(); |
| TrafficTreatment drop = DefaultTrafficTreatment.builder() |
| .drop().build(); |
| |
| flowObjectiveService.forward(deviceId, DefaultForwardingObjective.builder() |
| .fromApp(appId) |
| .withSelector(selector) |
| .withTreatment(drop) |
| .withFlag(ForwardingObjective.Flag.VERSATILE) |
| .withPriority(DROP_PRIORITY) |
| .makeTemporary(TIMEOUT_SEC) |
| .add()); |
| } |
| |
| |
| // Indicates whether the specified packet corresponds to ICMP ping. |
| private boolean isIcmpPing(Ethernet eth) { |
| return eth.getEtherType() == Ethernet.TYPE_IPV4 && |
| ((IPv4) eth.getPayload()).getProtocol() == IPv4.PROTOCOL_ICMP; |
| } |
| |
| |
| // Intercepts packets |
| private class PingPacketProcessor implements PacketProcessor { |
| @Override |
| public void process(PacketContext context) { |
| Ethernet eth = context.inPacket().parsed(); |
| if (isIcmpPing(eth)) { |
| processPing(context, eth); |
| } |
| } |
| } |
| |
| // Record of a ping between two end-station MAC addresses |
| private class PingRecord { |
| private final MacAddress src; |
| private final MacAddress dst; |
| |
| PingRecord(MacAddress src, MacAddress dst) { |
| this.src = src; |
| this.dst = dst; |
| } |
| |
| @Override |
| public int hashCode() { |
| return Objects.hash(src, dst); |
| } |
| |
| @Override |
| public boolean equals(Object obj) { |
| if (this == obj) { |
| return true; |
| } |
| if (obj == null || getClass() != obj.getClass()) { |
| return false; |
| } |
| final PingRecord other = (PingRecord) obj; |
| return Objects.equals(this.src, other.src) && Objects.equals(this.dst, other.dst); |
| } |
| } |
| |
| // Prunes the given ping record from the specified device. |
| private class PingPruner extends TimerTask { |
| private final DeviceId deviceId; |
| private final PingRecord ping; |
| |
| public PingPruner(DeviceId deviceId, PingRecord ping) { |
| this.deviceId = deviceId; |
| this.ping = ping; |
| } |
| |
| @Override |
| public void run() { |
| pings.remove(deviceId, ping); |
| } |
| } |
| |
| // Listens for our removed flows. |
| private class InternalFlowListener implements FlowRuleListener { |
| @Override |
| public void event(FlowRuleEvent event) { |
| FlowRule flowRule = event.subject(); |
| if (event.type() == RULE_REMOVED && flowRule.appId() == appId.id()) { |
| Criterion criterion = flowRule.selector().getCriterion(ETH_SRC); |
| MacAddress src = ((EthCriterion) criterion).mac(); |
| MacAddress dst = ((EthCriterion) criterion).mac(); |
| log.warn(MSG_PING_REENABLED, src, dst, flowRule.deviceId()); |
| } |
| } |
| } |
| } |