package net.floodlightcontroller.topologysecurity;
import java.io.IOException;
import java.math.BigInteger;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.*;
import org.openflow.protocol.OFMessage;
import org.openflow.protocol.OFPacketIn;
import org.openflow.protocol.OFPacketOut;
import org.openflow.protocol.OFPort;
import org.openflow.protocol.OFType;
import org.openflow.protocol.action.OFAction;
import org.openflow.protocol.action.OFActionOutput;
import org.slf4j.LoggerFactory;
import ch.qos.logback.classic.Logger;
import net.floodlightcontroller.core.FloodlightContext;
import net.floodlightcontroller.core.IFloodlightProviderService;
import net.floodlightcontroller.core.IOFMessageListener;
import net.floodlightcontroller.core.IOFSwitch;
import net.floodlightcontroller.core.IOFSwitch.PortChangeType;
import net.floodlightcontroller.core.IOFSwitchListener;
import net.floodlightcontroller.core.ImmutablePort;
import net.floodlightcontroller.core.module.FloodlightModuleContext;
import net.floodlightcontroller.core.module.FloodlightModuleException;
import net.floodlightcontroller.core.module.IFloodlightModule;
import net.floodlightcontroller.core.module.IFloodlightService;
import net.floodlightcontroller.devicemanager.IDevice;
import net.floodlightcontroller.devicemanager.IDeviceListener;
import net.floodlightcontroller.devicemanager.IDeviceService;
import net.floodlightcontroller.devicemanager.SwitchPort;
import net.floodlightcontroller.linkdiscovery.ILinkDiscoveryListener;
import net.floodlightcontroller.linkdiscovery.ILinkDiscoveryService;
import net.floodlightcontroller.packet.ARP;
import net.floodlightcontroller.packet.Data;
import net.floodlightcontroller.packet.Ethernet;
import net.floodlightcontroller.packet.ICMP;
import net.floodlightcontroller.packet.IPacket;
import net.floodlightcontroller.packet.IPv4;
import net.floodlightcontroller.packet.LLDP;
import net.floodlightcontroller.util.MACAddress;
class HostEntity{
MACAddress mac;
InetAddress ip;
public HostEntity(MACAddress mac, InetAddress ip){
this.mac = mac;
this.ip = ip;
}
}
class Port {
Long sw; //dpid of switch
Short port_number;
public Port(Long dpid, Short port) {
this.sw = dpid;
this.port_number = port;
}
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final Port other = (Port) obj;
if (this.sw != other.sw) {
return false;
}
if (this.port_number != other.port_number) {
return false;
}
return true;
}
// naiive version of hash computation
@Override
public int hashCode() {
int hash = 3;
hash = 67 * hash + this.sw.hashCode();
hash = 67 * hash + this.port_number;
return hash;
}
};
public class TopoloyUpdateChecker implements IDeviceListener,
ILinkDiscoveryListener, IFloodlightModule {
private static final HostEntity[] HostEntity = null;
protected IFloodlightProviderService floodlightProvider;
protected IDeviceService deviceService;
protected ILinkDiscoveryService linkDiscoveryService;
protected static Logger logger;
// Implementation of PortManager
protected PortManager portManager;
protected HostProber hostProber;
protected static String ControllerIP = "10.0.1.100";
@Override
public String getName() {
// TODO Auto-generated method stub
return null;
}
@Override
public boolean isCallbackOrderingPrereq(String type, String name) {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean isCallbackOrderingPostreq(String type, String name) {
// TODO Auto-generated method stub
return false;
}
@Override
public void deviceAdded(IDevice device) {
logger.info("Device:{} is added on:", device.getMACAddressString());
for (SwitchPort sp : device.getAttachmentPoints()) {
logger.info("sw:{},port:{}", sp.getSwitchDPID(), sp.getPort());
}
}
@Override
public void deviceRemoved(IDevice device) {
// TODO Auto-generated method stub
}
@Override
public void deviceMoved(IDevice device) {
// convert src mac address to MACAddress
MACAddress src_mac = MACAddress.valueOf(device.getMACAddress());
SwitchPort[] previousLocation = device.getOldAP();
if (previousLocation.length == 1) { // only one location in AP history
long previousDpid= previousLocation[0].getSwitchDPID();
short previousPortID = (short) previousLocation[0].getPort();
Port previousPort = new Port(previousDpid, previousPortID);
Map<MACAddress, Boolean> hosts = this.portManager.port_list
.get(previousPort).hosts;
if (!hosts.containsKey(src_mac)){
//logger.error("can not read previous host location about {}",src_mac);
return;
}
if (hosts.get(src_mac) == false) { // indicating the host is not disabled
//Violation: no port shutdown signal is received during host move
logger.warn("Violation: Host Move from switch {} port {} without Port ShutDown"
, previousDpid, previousPortID);
}
//host prober to send ARP Ping to testify the liveness of host
InetAddress src_ip;
try {
if (device.getIPv4Addresses().length < 1){
return;
}
src_ip = InetAddress.getByAddress(BigInteger.valueOf(device.getIPv4Addresses()[0]).toByteArray());
if (hostProber.sendHostProbe(src_ip, src_mac, previousDpid, previousPortID)){
List<HostEntity> hostEntity = hostProber.probedPorts.get(previousPort);
if (hostEntity == null){
hostEntity = new ArrayList<HostEntity>();
}
hostEntity.add(new HostEntity(src_mac, src_ip));
hostProber.probedPorts.put(previousPort, hostEntity);
}
} catch (UnknownHostException e) {
logger.error("Can not convert string to InetAddress,{}",e.getMessage());
}
}
}
@Override
public void deviceIPV4AddrChanged(IDevice device) {
// TODO Auto-generated method stub
}
@Override
public void deviceVlanChanged(IDevice device) {
// TODO Auto-generated method stub
}
@Override
public Collection<Class<? extends IFloodlightService>> getModuleServices() {
// TODO Auto-generated method stub
return null;
}
@Override
public Map<Class<? extends IFloodlightService>, IFloodlightService> getServiceImpls() {
// TODO Auto-generated method stub
return null;
}
@Override
public Collection<Class<? extends IFloodlightService>> getModuleDependencies() {
Collection<Class<? extends IFloodlightService>> l = new ArrayList<Class<? extends IFloodlightService>>();
l.add(IFloodlightProviderService.class);
l.add(ILinkDiscoveryService.class);
l.add(IDeviceService.class);
return l;
}
@Override
public void init(FloodlightModuleContext context)
throws FloodlightModuleException {
floodlightProvider = context
.getServiceImpl(IFloodlightProviderService.class);
deviceService = context.getServiceImpl(IDeviceService.class);
linkDiscoveryService = context
.getServiceImpl(ILinkDiscoveryService.class);
logger = (Logger) LoggerFactory.getLogger(PortManager.class);
portManager = new PortManager();
hostProber = new HostProber();
}
@Override
public void startUp(FloodlightModuleContext context)
throws FloodlightModuleException {
deviceService.addListener(this);
linkDiscoveryService.addListener(this);
// Register for the OpenFlow messages we want to receive for PortManager
floodlightProvider.addOFMessageListener(OFType.PACKET_IN,
this.portManager);
// Register for switch updates for PortManager
floodlightProvider.addOFSwitchListener(this.portManager);
}
/*
* check link update
*/
@Override
public void linkDiscoveryUpdate(LDUpdate update) {
// logger.error("receive single link update");
}
@Override
public void linkDiscoveryUpdate(List<LDUpdate> updateList) {
// logger.error("receive boundle link update");
}
class PortManager implements IOFMessageListener, IOFSwitchListener {
protected Map<Port, PortProperty> port_list; // the port list to keep
// port property
protected Map<MACAddress, Port> mac_port; // for quick locate host
// location
public PortManager() {
port_list = new HashMap<Port, PortProperty>();
mac_port = new HashMap<MACAddress, Port>();
}
@Override
public String getName() {
return "Port Manager";
}
@Override
public boolean isCallbackOrderingPrereq(OFType type, String name) {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean isCallbackOrderingPostreq(OFType type, String name) {
// in case other modules eat Packet-In message
return (type.equals(OFType.PACKET_IN) && (name.equals("topology")
|| name.equals("linkdiscovery") || name
.equals("devicemanager")));
}
@Override
public Command receive(IOFSwitch sw, OFMessage msg,
FloodlightContext cntx) {
switch (msg.getType()) {
case PACKET_IN:
return this.processPacketInMessage(sw, (OFPacketIn) msg, cntx);
default:
break;
}
return Command.CONTINUE;
}
// ******************
// IOFSwitchListener
// ******************
@Override
public void switchAdded(long switchId) {
this.handleSwitchAdd(switchId);
}
@Override
public void switchRemoved(long switchId) {
// TODO delete corresponding ports?
}
@Override
public void switchActivated(long switchId) {
// do nothing
}
@Override
public void switchPortChanged(long switchId, ImmutablePort port,
PortChangeType type) {
switch (type) {
case DELETE:
case DOWN:
this.handlePortDown(switchId, port);
break;
default:
break;
}
}
@Override
public void switchChanged(long switchId) {
// do nothing
}
// ******************
// Message Handler
// ******************
/*
*
*/
void handleSwitchAdd(long dpid) {
IOFSwitch sw = floodlightProvider.getSwitch(dpid);
for (ImmutablePort port : sw.getPorts()) {
Port switch_port = new Port(dpid, port.getPortNumber());
if (!this.port_list.containsKey(switch_port)) {
this.port_list.put(switch_port, new PortProperty());
}
}
}
/*
* we disable all attached hosts if the port shut down
*/
void handlePortDown(long dpid, ImmutablePort port) {
Port switch_port = new Port(dpid, port.getPortNumber());
PortProperty pp = this.port_list.get(switch_port);
pp.receivePortDown();
this.port_list.put(switch_port, pp);
}
Command processPacketInMessage(IOFSwitch sw, OFPacketIn pi,
FloodlightContext cntx) {
Ethernet eth = IFloodlightProviderService.bcStore.get(cntx,
IFloodlightProviderService.CONTEXT_PI_PAYLOAD);
long dpid = sw.getId();
short inport = pi.getInPort();
Port switch_port = new Port(dpid, inport);
MACAddress src_mac = eth.getSourceMAC();
PortProperty pp = this.port_list.get(switch_port);
if (pp == null)
return Command.CONTINUE;
DeviceType dt = pp.getDeviceType();
// testify if the packet is the response to Host Probing
if (eth.isBroadcast())
return Command.CONTINUE;
try {
if (eth.getEtherType() == Ethernet.TYPE_IPv4){
IPv4 ip = (IPv4) eth.getPayload();
InetAddress srcIP = InetAddress.getByAddress(
BigInteger.valueOf(ip.getSourceAddress()).toByteArray());
InetAddress dstIP = InetAddress.getByAddress(
BigInteger.valueOf(ip.getDestinationAddress()).toByteArray());
if(ip.getProtocol() == IPv4.PROTOCOL_ICMP){
ICMP icmp = (ICMP) ip.getPayload();
if (icmp.getIcmpCode() == ICMP.ECHO_REPLY){
if (hostProber.probedPorts.containsKey(switch_port)){
for (HostEntity he : hostProber.probedPorts.get(switch_port)){
if (he.ip.equals(srcIP) && he.mac.equals(eth.getSourceMAC()) &&
InetAddress.getByName(ControllerIP).equals(dstIP)){
//we think this is the response to host probing
//Violation: host is still reachable at previous locaiton
logger.warn("Violation: Host Move from switch {} port {} is still reachable"
, dpid, inport);
List<HostEntity> hostEntity = hostProber.probedPorts.get(switch_port);
hostEntity.remove(he);
hostProber.probedPorts.put(switch_port, hostEntity);
return Command.STOP;
}
}
}
}
}
}
} catch (UnknownHostException e) {
logger.error("Cannot construct InetAddress, {}", e.getMessage());
}
// if the payload is lldp, we do not verify correctness of LLDP in this point
if (eth.getPayload() instanceof LLDP) {
if (dt == DeviceType.ANY) {
pp.setPortSwitch();
} else if (dt == DeviceType.HOST) {
// Violation: impossible to receive LLDP from HOST port, Countermeasure: eat the lldp
logger.warn("Violation: Receive LLDP packets from HOST port: SW {} port {}"
, dpid, inport);
return Command.STOP;
}
}
// if the payload is host traffic
else {
// if this host found before
if (this.mac_port.containsKey(src_mac)) {
Port host_location = this.mac_port.get(src_mac);
if (host_location == switch_port) {
// this port is first hop port for found host
if (dt == DeviceType.SWITCH) {
// Violation: receive first hop traffic from SWITH port
logger.warn("Violation: Receive first hop host packets from SWITCH port: SW {} port {}"
, dpid, inport);
//return Command.STOP;
} else if (dt == DeviceType.ANY) {
//this happened if the port is shutdown
pp.setPortHost();
pp.disableHostShutDown(src_mac);
this.port_list.put(switch_port, pp);
}
}
}
else { // new host
this.mac_port.put(src_mac, switch_port);
pp.addHost(src_mac);
pp.setPortAny();
this.port_list.put(switch_port, pp);
}
}
return Command.CONTINUE;
}
}
class HostProber{
MACAddress senderMAC;
MACAddress targetMAC;
MACAddress broadcastMAC;
MACAddress emptyMAC;
InetAddress senderIP;
InetAddress targetIP;
//we use Port as key, since it is not common that one host entity attach to multiple ports
Map<Port, List<HostEntity>> probedPorts;
public HostProber(){
senderMAC = MACAddress.valueOf("aa:aa:aa:aa:aa:aa");
broadcastMAC = MACAddress.valueOf("ff:ff:ff:ff:ff:ff");
emptyMAC = MACAddress.valueOf("00:00:00:00:00:00");
probedPorts = new HashMap<Port, List<HostEntity>>();
try {
senderIP = InetAddress.getByName(ControllerIP);
} catch (UnknownHostException e) {
logger.error("Cannot construct InetAddress, {}", e.getMessage());
}
}
protected boolean sendHostProbe(InetAddress ip, MACAddress mac, long dpid, short port){
byte[] data;
//Currently, we use ICMPing to probe the host
// data = generateARPPing(ip);
data = generateICMPPing(ip, mac);
IOFSwitch sw = floodlightProvider.getSwitch(dpid);
if (sw == null) {
return false;
}
ImmutablePort ofpPort = sw.getPort(port);
if (ofpPort == null) {
if (logger.isTraceEnabled()) {
logger.trace("Null physical port. sw={}, port={}", sw, port);
}
return false;
}
OFPacketOut po = (OFPacketOut) floodlightProvider.getOFMessageFactory().getMessage(OFType.PACKET_OUT);
po.setBufferId(OFPacketOut.BUFFER_ID_NONE);
po.setInPort(OFPort.OFPP_NONE);
// set actions
List<OFAction> actions = new ArrayList<OFAction>();
actions.add(new OFActionOutput(port, (short) 0));
po.setActions(actions);
po.setActionsLength((short) OFActionOutput.MINIMUM_LENGTH);
// set data
po.setLengthU(OFPacketOut.MINIMUM_LENGTH + po.getActionsLength() + data.length);
po.setPacketData(data);
// send
try {
sw.write(po, null);
sw.flush();
} catch (IOException e) {
logger.error("Failure sending host probe out port {} on switch {}",
new Object[]{ port, sw.getStringId() }, e);
return false;
}
return true;
}
protected byte[] generateICMPPing(InetAddress ip, MACAddress mac){
targetIP = ip;
targetMAC = mac;
IPacket packet = new IPv4()
.setProtocol(IPv4.PROTOCOL_ICMP)
.setSourceAddress(ControllerIP)
.setDestinationAddress(ip.hashCode()) //this is tricky
.setPayload(new ICMP()
.setIcmpType((byte) 8)
.setIcmpCode((byte) 0)
.setPayload(new Data(new byte[]
{0x76, (byte) 0xf2, 0x0, 0x2, 0x1, 0x1, 0x1}))
);
Ethernet ethernet = new Ethernet().setSourceMACAddress(senderMAC.toBytes())
.setDestinationMACAddress(targetMAC.toBytes())
.setEtherType(Ethernet.TYPE_IPv4);
ethernet.setPayload(packet);
return ethernet.serialize();
}
protected byte[] generateARPPing(InetAddress ip, MACAddress mac){
targetIP = ip;
ARP arp = new ARP().setHardwareType(ARP.HW_TYPE_ETHERNET)
.setProtocolType(ARP.PROTO_TYPE_IP)
.setHardwareAddressLength((byte) 6)
.setProtocolAddressLength((byte) 4)
.setOpCode(ARP.OP_RARP_REQUEST)
.setSenderHardwareAddress(senderMAC.toBytes())
.setSenderProtocolAddress(senderIP.getAddress())
.setTargetHardwareAddress(emptyMAC.toBytes())
.setTargetProtocolAddress(targetIP.getAddress());
Ethernet ethernet = new Ethernet().setSourceMACAddress(senderMAC.toBytes())
.setDestinationMACAddress(broadcastMAC.toBytes())
.setEtherType(Ethernet.TYPE_ARP);
ethernet.setPayload(arp);
return ethernet.serialize();
}
}
}