/** * Copyright (c) <2013> <Radware Ltd.> and others. All rights reserved. * * This program and the accompanying materials are made available under the terms of the Eclipse Public License * v1.0 which accompanies this distribution, and is available at http://www.eclipse.org/legal/epl-v10.html * @author Gera Goft * @author Konstantin Pozdeev * @version 0.1 */ package org.opendaylight.defense4all.core.impl; import java.util.Hashtable; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Properties; import java.util.Set; import org.opendaylight.defense4all.core.Attack; import org.opendaylight.defense4all.core.AttackDecisionPoint; import org.opendaylight.defense4all.core.DFAppRoot; import org.opendaylight.defense4all.core.Detection; import org.opendaylight.defense4all.core.PN; import org.opendaylight.defense4all.core.ProtocolPort; import org.opendaylight.defense4all.core.Traffic; import org.opendaylight.defense4all.core.Attack.Status; import org.opendaylight.defense4all.core.PN.OperationalStatus; import org.opendaylight.defense4all.core.ProtocolPort.DFProtocol; import org.opendaylight.defense4all.core.Traffic.TrafficMatch; import org.opendaylight.defense4all.core.interactionstructures.EndDetectionNotification; import org.opendaylight.defense4all.framework.core.ExceptionControlApp; import org.opendaylight.defense4all.framework.core.FMHolder; import org.opendaylight.defense4all.framework.core.HealthTracker; import org.opendaylight.defense4all.framework.core.FrameworkMain.ResetLevel; public class AttackDecisionPointImpl extends DFAppCoreModule implements AttackDecisionPoint { /** * Decoupled actions for ActionSwitcher */ protected static final int ACTION_INVALID = -1; // Already defined in Module. Brought here for brevity protected static final int ACTION_RESERVED = 0; // Already defined in Module. Brought here for brevity protected static final int ACTION_PROCESS_ATTACKS = 1; protected static final int ACTION_REMOVE_PN = 2; /* Use the values set below if not set anywhere else */ protected long processAttacksPeriod = 1000; protected boolean initialized = false; public AttackDecisionPointImpl() { super(); } /* Setters for Spring */ public void setProcessAttacksPeriod(int period) { this.processAttacksPeriod = period;} /** Post-constructor initialization */ public void init() throws ExceptionControlApp { super.init(); log.info( "AttackDecisionPoint is starting."); addPeriodicExecution(ACTION_PROCESS_ATTACKS, null, processAttacksPeriod); initialized = true; } /** Pre-shutdown cleanup */ public void finit() { log.info( "AttackDecisionPoint is stopping."); super.finit(); } /** Reset * @throws ExceptionControlApp */ public synchronized void reset(ResetLevel resetLevel) throws ExceptionControlApp { log.info( "AttackDecisionPoint is resetting to level " + resetLevel); super.reset(resetLevel); Traffic.NameHash.reset(); try { dfAppRootFullImpl.detectionsRepo.truncate(); dfAppRootFullImpl.attacksRepo.truncate(); } catch (Throwable e) { log.error("Failed to reset detectionsRepo and/or attacksRepo", e ); } } /** Test * @throws ExceptionControlApp */ public void test() throws ExceptionControlApp { test2(); } protected void test2() throws ExceptionControlApp { Attack attack = new Attack("pn1_TCP:0", "pn1", new ProtocolPort(DFProtocol.TCP, 0), Status.DECLARED, new Properties()); attack.detectionKeys.setProperty("df_detectorpn1.TCP:0", "df_detectorpn1.TCP:0"); dfAppRootFullImpl.attacksRepo.setRow(attack.key, attack.toRow()); Attack check; long start = System.currentTimeMillis() / 1000; for(int i=0;i<10000;i++) { int j = ((int) Math.random()) % 4; switch(j) { case 0: attack = new Attack(dfAppRootFullImpl.attacksRepo.getRow("pn1_TCP:0")); attack.detectionKeys.setProperty("df_detectorpn1.TCP:0", "df_detectorpn1.TCP:0"); dfAppRootFullImpl.attacksRepo.setRow(attack.key, attack.toRow()); check = new Attack(dfAppRootFullImpl.attacksRepo.getRow("pn1_TCP:0")); if(attack.detectionKeys.size() != check.detectionKeys.size()) System.out.println(attack.detectionKeys.toString() + "; " + check.detectionKeys.toString()); break; case 1: attack = new Attack(dfAppRootFullImpl.attacksRepo.getRow("pn1_TCP:0")); attack.detectionKeys.setProperty("dp_based_detectorpn1.TCP:0", "dp_based_detectorpn1.TCP:0"); dfAppRootFullImpl.attacksRepo.setRow(attack.key, attack.toRow()); check = new Attack(dfAppRootFullImpl.attacksRepo.getRow("pn1_TCP:0")); if(attack.detectionKeys.size() != check.detectionKeys.size()) System.out.println(attack.detectionKeys.toString() + "; " + check.detectionKeys.toString()); break; case 2: attack = new Attack(dfAppRootFullImpl.attacksRepo.getRow("pn1_TCP:0")); attack.detectionKeys.setProperty("dp_based_detectorpn1.TCP:80", "dp_based_detectorpn1.TCP:80"); dfAppRootFullImpl.attacksRepo.setRow(attack.key, attack.toRow()); check = new Attack(dfAppRootFullImpl.attacksRepo.getRow("pn1_TCP:0")); if(attack.detectionKeys.size() != check.detectionKeys.size()) System.out.println(attack.detectionKeys.toString() + "; " + check.detectionKeys.toString()); break; case 3: attack = new Attack(dfAppRootFullImpl.attacksRepo.getRow("pn1_TCP:0")); attack.detectionKeys.clear(); dfAppRootFullImpl.attacksRepo.setRow(attack.key, attack.toRow()); System.out.println("Cleared detectionKeys"); break; default: break; } } long end = System.currentTimeMillis() / 1000; System.out.println("time = " + (end-start)); } /** * #### method description #### * @param param_name param description * @return return description * @throws ExceptionControlApp * @throws exception_type circumstances description */ public synchronized void addDetection(Detection detection) { /* Record the detection in detections repo */ if (detection== null || detection.key == null ) { log.error("Trying to add null detection"); return; } /* Check if the PN is failed or canceled */ try { Object obj = dfAppRootFullImpl.pNsRepo.getCellValue(detection.pnKey, PN.OPERATIONAL_STATUS); OperationalStatus operationalStatus = OperationalStatus.INVALID; if(obj != null) operationalStatus = OperationalStatus.valueOf((String) obj); if(operationalStatus == OperationalStatus.CANCELED || operationalStatus == OperationalStatus.FAILED) return; } catch (Throwable e) { log.error("Failed to retrieve operational status value for " + detection.pnKey, e); fMain.getHealthTracker().reportHealthIssue(HealthTracker.MINOR_HEALTH_ISSUE); return; } log.info("DF is processing detection "+detection.toString()); try { dfAppRootFullImpl.detectionsRepo.setRow(detection.key, detection.toRow()); } catch (ExceptionControlApp e) { log.error("Failed to add detetection to detectionsRepo. Detection key: "+detection.key, e); fr.logRecord(DFAppRoot.FR_DF_FAILURE,"DefenseFlow failed to process detection "+Detection.getPrintableDetectionTarget(detection.key)); fMain.getHealthTracker().reportHealthIssue(HealthTracker.MINOR_HEALTH_ISSUE); return; } /* Record the detection also in attacks repo. Create new attack if no matching attack exists yet. */ Attack attack = findMatchingAttack(detection); String newAttackKey = Attack.generateKey(detection.pnKey, detection.protocolPort); if(attack == null) { attack = new Attack(newAttackKey, detection.pnKey, detection.protocolPort, Status.SUSPECTED, null); fr.logRecord(DFAppRoot.FR_DF_SECURITY, "DefenseFlow declaring a new attack on " +attack.getPrintableAttackTarget()); } switch(attack.status) { case DECLARED: log.info( "DF is adding detection " + detection.key + " to existing attack " + attack.key); break; case SUSPECTED: break; case ENDED: try { dfAppRootFullImpl.attacksRepo.deleteRow(attack.key); // Attack can be deleted - cleanup completed } catch (Throwable e) { log.error("Failed to delete attack from attacksRepo. " +attack.key, e); fMain.getHealthTracker().reportHealthIssue(HealthTracker.MINOR_HEALTH_ISSUE); } attack = new Attack(newAttackKey, detection.pnKey, detection.protocolPort, Status.SUSPECTED, null); fr.logRecord(DFAppRoot.FR_DF_SECURITY, "DefenseFlow declaring a new attack on " +Detection.getPrintableDetectionTarget( newAttackKey)); log.info("DF is declaring a new attack " + newAttackKey); break; case ENDING: log.info(detection.key + " is of an \"ending attack\". " + "DF is ignoring new detections until the attack is fully ended."); return; // Ignore new detections until ended attack is deleted default: return; } attack.detectionKeys.setProperty(detection.key, detection.key); try { dfAppRootFullImpl.attacksRepo.setRow(attack.key, attack.toRow()); } catch (Throwable e) { log.error("Failed to create attack in the attacksRepo. " +attack.key, e); fr.logRecord(DFAppRoot.FR_DF_SECURITY, "DefenseFlow fail to process attack on " +Detection.getPrintableDetectionTarget( attack.key)); fMain.getHealthTracker().reportHealthIssue(HealthTracker.MINOR_HEALTH_ISSUE); return; } /* Invoke the processAttacks now. (Never mind if fails - it is invoked periodically anyway). */ try { invokeDecoupledSerially(ACTION_PROCESS_ATTACKS, null); // Invoke to check status of all attacks (process all detections) } catch (Throwable e) { log.error("Excepted trying to invokeDecoupledSerialiy " + ACTION_PROCESS_ATTACKS , e); } } /** * #### method description #### * @param param_name param description * @return return description * @throws ExceptionControlApp * @throws exception_type circumstances description */ protected Attack findMatchingAttack(Detection detection) { Attack attack; PN pn; try { pn = new PN(dfAppRootFullImpl.pNsRepo.getRow(detection.pnKey)); } catch (Throwable e1) { log.error("Failed to inflate PN from repo for detection: " + detection.key + "PN key:" + detection.pnKey, e1); fMain.getHealthTracker().reportHealthIssue(HealthTracker.MINOR_HEALTH_ISSUE); return null; } Traffic comparedAttackTraffic = new Traffic(0, pn.dstAddr, pn.dstAddrPrefixLen); //vlan=0 - not important. same PN compared Hashtable<String,Hashtable<String,Object>> attackTable = dfAppRootFullImpl.attacksRepo.getTable(); if(attackTable == null) { log.error("Retrieved null attacksTable."); fMain.getHealthTracker().reportHealthIssue(HealthTracker.SIGNIFICANT_HEALTH_ISSUE); return null; } Iterator<Map.Entry<String,Hashtable<String,Object>>> iter = attackTable.entrySet().iterator(); Map.Entry<String,Hashtable<String,Object>> entry; Hashtable<String,Object> attackRow; TrafficMatch trafficMatch; while(iter.hasNext()) { entry = iter.next(); try { attackRow = entry.getValue(); attack = new Attack(attackRow); } catch (Throwable e) { log.error("Excepted trying to inflate attack from row. " , e); FMHolder.get().getHealthTracker().reportHealthIssue(HealthTracker.MINOR_HEALTH_ISSUE); continue; } if(!attack.pnKey.equals(detection.pnKey)) continue; /* Matched pnKey means matched dstAddr and dstAddrPrefixLen, so match proto-port */ comparedAttackTraffic.setProtocolPort(attack.protocolPort.protocol,attack.protocolPort.port); try { trafficMatch = comparedAttackTraffic.match(0, pn.dstAddr, detection.protocolPort.protocol, detection.protocolPort.port); // vlan = 0 is not important here, because comparison is for the same PN } catch (Throwable e) { log.error("Excepted in match operation for attack traffic: "+ pn.dstAddr.getHostAddress()+":"+ detection.protocolPort.protocol+"."+ detection.protocolPort.port, e); FMHolder.get().getHealthTracker().reportHealthIssue(HealthTracker.MINOR_HEALTH_ISSUE); continue; } if(trafficMatch == TrafficMatch.NO) continue; return attack; // Found matching attack } return null; // Did not find matching attack } /** * #### method description #### * @param param_name param description * @return return description * @throws ExceptionControlApp * @throws exception_type circumstances description */ public synchronized void removeDetection(String detectionKey) { fr.logRecord(DFAppRoot.FR_DF_SECURITY, "DefenseFlow removing detection for " + Detection.getPrintableDetectionTarget(detectionKey)); log.info("DF is removing detection " + detectionKey); Hashtable<String, Object> detectionRow; try { detectionRow = dfAppRootFullImpl.detectionsRepo.getRow(detectionKey); } catch (ExceptionControlApp e) { log.error("Failed to get detetectionRow from detectionsRepo. Detection key: "+detectionKey, e); fr.logRecord(DFAppRoot.FR_DF_FAILURE, "DefenseFlow failed to remove detection for " +Detection.getPrintableDetectionTarget(detectionKey)); fMain.getHealthTracker().reportHealthIssue(HealthTracker.MINOR_HEALTH_ISSUE); return; } if(detectionRow == null) return; Detection detection = new Detection(detectionRow); /* Remove the detection also in attacks repo - not very efficient, but clear */ try { Hashtable<String,Hashtable<String,Object>> attackTable = dfAppRootFullImpl.attacksRepo.getTable(); Iterator<Map.Entry<String,Hashtable<String,Object>>> iter = attackTable.entrySet().iterator(); Map.Entry<String,Hashtable<String,Object>> entry; Properties detectionKeys; while(iter.hasNext()) { entry = iter.next(); detectionKeys = (Properties) entry.getValue().get(Attack.DETECTION_KEYS); if(detectionKeys == null) continue; detectionKeys.remove(detection.key); dfAppRootFullImpl.attacksRepo.setCell(entry.getKey(), Attack.DETECTION_KEYS, detectionKeys); } } catch (Throwable e) { log.error("Failed to delete detection from the attack. Detection key: "+detection.key, e); fMain.getHealthTracker().reportHealthIssue(HealthTracker.MINOR_HEALTH_ISSUE); return; } /* Remove the detection from detections repo, but hold on to it for removal from attacks repo */ try { dfAppRootFullImpl.detectionsRepo.deleteRow(detectionKey); } catch (ExceptionControlApp e1) { log.error("Failed to delete detetectionRow from detectionsRepo. Detection key: "+detectionKey, e1); fMain.getHealthTracker().reportHealthIssue(HealthTracker.MINOR_HEALTH_ISSUE); return; } // Piggyback processing. For possible name changes Traffic.NameHash.reset(); try { invokeDecoupledSerially(ACTION_PROCESS_ATTACKS, null); // To process all detections. } catch (Throwable e) { log.error("Excepted trying to invokeDecoupledSerialiy " + ACTION_PROCESS_ATTACKS, e); } } /** * #### method description #### * @param param_name param description * @return return description * @throws ExceptionControlApp * @throws exception_type circumstances description */ public void removePN(String pnKey) throws ExceptionControlApp { log.info( "DF is processing PN removal with respect to ongoing attacks " + pnKey); try { invokeDecoupledSerially(ACTION_REMOVE_PN, pnKey); } catch (ExceptionControlApp e) { String msg = "Excepted trying to invokeDecoupledSerialiy " + ACTION_REMOVE_PN + " " + pnKey; log.error(msg, e); throw new ExceptionControlApp(msg, e); } } /** * #### method description #### * @param param_name param description * @return return description * @throws ExceptionControlApp * @throws exception_type circumstances description */ protected synchronized void decoupledRemovePN(String pnKey) { Hashtable<String,Hashtable<String,Object>> attackTable = dfAppRootFullImpl.attacksRepo.getTable(); if (attackTable == null ) return; Iterator<Map.Entry<String,Hashtable<String,Object>>> iter = attackTable.entrySet().iterator(); Map.Entry<String,Hashtable<String,Object>> entry; Hashtable<String,Object> attackRow; String attackPnkey; /* Check if there are any attacks for this PN, and end them */ while(iter.hasNext()) { try { entry = iter.next(); attackRow = entry.getValue(); attackPnkey = (String) attackRow.get(Attack.PNKEY); if(attackPnkey.equals(pnKey)) { String msg = "DefenseFlow ending attack for removed PO " + PN.getPrintableKey(pnKey); fr.logRecord(DFAppRoot.FR_DF_SECURITY, msg); endAttack(new Attack(attackRow)); } } catch (ExceptionControlApp e) { log.error("Failed to process endAttack in decoupledRemovePN ", e); } } } /** * #### method description #### * @param param_name param description * @return return description * @throws ExceptionControlApp * @throws exception_type circumstances description */ protected synchronized void periodicProcessAttacks() { if(!fMain.isOpenForBusiness()) return; // Operate only after everything is initialized and is not terminating List<String> attackKeys = null; try { attackKeys = dfAppRootFullImpl.attacksRepo.getKeys(); } catch (ExceptionControlApp e) { log.error("Failed to get keys from attacksRepo", e); fMain.getHealthTracker().reportHealthIssue(HealthTracker.MINOR_HEALTH_ISSUE); } if ( attackKeys == null ) return; for(String attackKey : attackKeys) { try { processAttack(attackKey); } catch (ExceptionControlApp e) { log.error("Failed in decoupledProcessAttacks. Attack key: "+attackKey); fMain.getHealthTracker().reportHealthIssue(HealthTracker.MINOR_HEALTH_ISSUE); } } } protected void processAttack(String attackKey) throws ExceptionControlApp { Hashtable<String, Object> attackRow; try { attackRow = dfAppRootFullImpl.attacksRepo.getRow(attackKey); } catch (Throwable e) { log.error("Failed to get attackRow from attacksRepo. Attack key: "+attackKey, e); fMain.getHealthTracker().reportHealthIssue(HealthTracker.MINOR_HEALTH_ISSUE); throw new ExceptionControlApp ("Failed to get attackRow from attacksRepo. Attack key: "+attackKey, e); } if(attackRow == null) return; Attack attack = new Attack(attackRow); if(attack.status == Status.ENDING) return; if(attack.status == Status.ENDED) { try { dfAppRootFullImpl.attacksRepo.deleteRow(attackKey); // Attack can be deleted - cleanup completed } catch (Exception e) { log.error("Failed to delete attackRow from attacksRepo. Attack key: "+attackKey, e); fMain.getHealthTracker().reportHealthIssue(HealthTracker.MINOR_HEALTH_ISSUE); throw new ExceptionControlApp ("Failed to delete attackRow from attacksRepo. Attack key: "+attackKey, e); } return; } Detection detection; long detectionExpiration; Hashtable<String,Object> detectionRow; long currentTime = System.currentTimeMillis() / 1000; long latestDetectionExpiration = currentTime; Set<Entry<Object, Object>> attackSet = attack.detectionKeys.entrySet(); if ( attackSet == null ) return; Iterator<Map.Entry<Object,Object>> iter = attackSet.iterator(); String detectionKey; while(iter.hasNext()) { detectionKey = (String) iter.next().getKey(); try { detectionRow = dfAppRootFullImpl.detectionsRepo.getRow(detectionKey); } catch (Throwable e) { log.error("Failed to get detectionRow from detectionsRepo. Detection key: "+detectionKey, e); fMain.getHealthTracker().reportHealthIssue(HealthTracker.MINOR_HEALTH_ISSUE); continue; } if(detectionRow == null) continue; detection = new Detection(detectionRow); detectionExpiration = detection.setTime + detection.duration; if(detectionExpiration > latestDetectionExpiration) { latestDetectionExpiration = detectionExpiration; } } log.debug("Attack: "+attackKey+" Expiration: "+ latestDetectionExpiration+ " Current time: "+currentTime); if(latestDetectionExpiration <= currentTime && attack.status != Status.ENDING && attack.status != Status.ENDED) { log.info("processAttack: ending attack: " + attack.toString()); endAttack(attack); // The attack should be ended } else if(attack.status != Status.DECLARED) { // New attack declareAttack(attackKey); } log.debug("attack detection keys: " + attack.detectionKeys.toString()); try { log.debug("attack repo detection keys: " + ((Properties) dfAppRootFullImpl.attacksRepo.getCellValue(attackKey, Attack.DETECTION_KEYS)).toString()); } catch (Exception e) {} // ignore // Else ongoing attack - nothing to be done } protected void declareAttack(String attackKey) throws ExceptionControlApp { // same message twice //fr.logRecord(DFAppRoot.FR_DF_SECURITY, "DefenseFlow declaring new attack on " + Attack.getPrintableAttackTarget(attackKey)); try { // clean-up counters from previous attacks dfAppRootFullImpl.detectorMgrImpl.cleanup(); } catch ( Throwable e) { log.error("Failed to initiate detector manager cleanup", e); // ignore }; try { dfAppRootFullImpl.attacksRepo.setCell(attackKey, Attack.STATUS, Status.DECLARED.name()); } catch (ExceptionControlApp e1) { log.error("Failed to update attack status in attacksRepo "+attackKey, e1); fr.logRecord(DFAppRoot.FR_DF_SECURITY, "DefenseFlow failed to process attack on " + Attack.getPrintableAttackTarget(attackKey)); fMain.getHealthTracker().reportHealthIssue(HealthTracker.MINOR_HEALTH_ISSUE); throw new ExceptionControlApp ("Failed to update attack status in attacksRepo "+attackKey, e1); } dfAppRootFullImpl.mitigationMgrImpl.mitigate(attackKey); // Update attack row } protected void endAttack(Attack attack) throws ExceptionControlApp { if(attack.status != Status.DECLARED) return; fr.logRecord(DFAppRoot.FR_DF_SECURITY, "DefenseFlow ending the attack on " + Attack.getPrintableAttackTarget(attack.key)); attack.status = Status.ENDING; List<String> mitigationTFKeys = dfAppRootFullImpl.mitigationMgrImpl.getMitigationTrafficFloorKeys(attack.key); Set<Entry<Object, Object>> detectionSet = attack.detectionKeys.entrySet(); if ( mitigationTFKeys != null && detectionSet != null ) { EndDetectionNotification endDetectionNotification = new EndDetectionNotification(); endDetectionNotification.trafficFloorKeys = mitigationTFKeys; Iterator<Map.Entry<Object,Object>> iter = detectionSet.iterator(); Map.Entry<Object,Object> entry; String detectionKey; Hashtable<String,Object> detectionRow; Detection detection; while(iter.hasNext()) { entry = iter.next(); detectionKey = (String) entry.getKey(); try { detectionRow = dfAppRootFullImpl.detectionsRepo.getRow(detectionKey); detection = new Detection(detectionRow); endDetectionNotification.detection = detection; dfAppRootFullImpl.detectorMgrImpl.notifyEndDetection(endDetectionNotification); dfAppRootFullImpl.detectionsRepo.deleteRow(detectionKey); } catch (Throwable e) { log.error("Failed to end detection for attack. "+detectionKey); // // don't process next - it will be retry on next circle // throw new ExceptionControlApp("Failed to end detection for attack. "+detectionKey); } iter.remove(); } } try { dfAppRootFullImpl.attacksRepo.setRow(attack.key, attack.toRow()); } catch (Exception e) { log.error("Failed to update attack in attacksRepo. "+attack.key); fr.logRecord(DFAppRoot.FR_DF_SECURITY, "DefenseFlow failed to end attack on " + Attack.getPrintableAttackTarget(attack.key)); fMain.getHealthTracker().reportHealthIssue(HealthTracker.MINOR_HEALTH_ISSUE); // don't process next - it will be retry on next circle throw new ExceptionControlApp("Failed to update attack in attacksRepo. "+attack.key); } try { dfAppRootFullImpl.mitigationMgrImpl.endMitigation(attack.key); } catch (Exception e) { // log.error("Excepted trying to request endMitigation from mitigation manager. "+attack.key ); // throw new ExceptionControlApp("Excepted trying to request endMitigation from mitigation manager. "+attack.key ); } } @Override protected void actionSwitcher(int actionCode, Object param) { switch(actionCode) { case ACTION_RESERVED: break; case ACTION_PROCESS_ATTACKS: periodicProcessAttacks(); break; case ACTION_REMOVE_PN: decoupledRemovePN((String) param); break; default: } } }