/* * Copyright 2011 Future Systems * * 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.krakenapps.ipmanager.impl; import java.net.InetAddress; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.concurrent.BlockingQueue; import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.LinkedBlockingQueue; import javax.persistence.EntityManager; import javax.persistence.NoResultException; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; import javax.persistence.criteria.Root; import org.apache.felix.ipojo.annotations.Component; import org.apache.felix.ipojo.annotations.Invalidate; import org.apache.felix.ipojo.annotations.Provides; import org.apache.felix.ipojo.annotations.Requires; import org.apache.felix.ipojo.annotations.Validate; import org.krakenapps.ipmanager.IpDetection; import org.krakenapps.ipmanager.IpEventListener; import org.krakenapps.ipmanager.IpManager; import org.krakenapps.ipmanager.IpQueryCondition; import org.krakenapps.ipmanager.LogQueryCondition; import org.krakenapps.ipmanager.model.Agent; import org.krakenapps.ipmanager.model.AllowedMac; import org.krakenapps.ipmanager.model.DeniedMac; import org.krakenapps.ipmanager.model.DetectedMac; import org.krakenapps.ipmanager.model.HostEntry; import org.krakenapps.ipmanager.model.HostNic; import org.krakenapps.ipmanager.model.IpEntry; import org.krakenapps.ipmanager.model.IpEventLog; import org.krakenapps.ipmanager.model.IpEventLog.Type; import org.krakenapps.jpa.ThreadLocalEntityManagerService; import org.krakenapps.jpa.handler.JpaConfig; import org.krakenapps.jpa.handler.Transactional; import org.krakenapps.pcap.decoder.ethernet.MacAddress; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @Component(name = "ipm-manager") @Provides @JpaConfig(factory = "ipm") public class IpManagerService implements IpManager, Runnable { private final Logger logger = LoggerFactory.getLogger(IpManagerService.class.getName()); @Requires private ThreadLocalEntityManagerService entityManagerService; private BlockingQueue<IpDetection> queue; private CopyOnWriteArraySet<IpEventListener> callbacks; private Thread t; private boolean doStop; @Validate public void start() { callbacks = new CopyOnWriteArraySet<IpEventListener>(); doStop = false; queue = new LinkedBlockingQueue<IpDetection>(); t = new Thread(this, "IP Sync"); t.start(); } @Invalidate public void stop() { callbacks.clear(); doStop = true; t.interrupt(); } @Transactional @SuppressWarnings("unchecked") @Override public List<Agent> getAgents(int orgId) { EntityManager em = entityManagerService.getEntityManager(); return em.createQuery("FROM Agent a WHERE a.orgId = ?").setParameter(1, orgId).getResultList(); } @SuppressWarnings("unchecked") @Transactional @Override public List<HostEntry> getHosts(int orgId) { EntityManager em = entityManagerService.getEntityManager(); return em.createQuery("SELECT h FROM HostEntry h INNER JOIN h.agent a WHERE a.orgId = ?") .setParameter(1, orgId).getResultList(); } @Transactional @Override public List<IpEntry> getIpEntries(IpQueryCondition condition) { EntityManager em = entityManagerService.getEntityManager(); CriteriaBuilder cb = em.getCriteriaBuilder(); CriteriaQuery<IpEntry> cq = cb.createQuery(IpEntry.class); Root<IpEntry> root = cq.from(IpEntry.class); cq.where(condition.getPredicate(cb, root)); return em.createQuery(cq).getResultList(); } @Transactional @Override public List<AllowedMac> getAllowMacAddresses(int orgId, int ipId) { EntityManager em = entityManagerService.getEntityManager(); IpEntry entry = (IpEntry) em.createQuery("FROM IpEntry i WHERE i.id = ?").setParameter(1, ipId) .getSingleResult(); if (entry.getAgent().getId() != orgId) return null; return entry.getAllowedMacs(); } @Transactional @Override public int allowMacAddress(int orgId, int ipId, String mac, Date from, Date to) { mac = mac.toUpperCase(); EntityManager em = entityManagerService.getEntityManager(); try { IpEntry ipEntry = (IpEntry) em .createQuery("SELECT e FROM IpEntry e INNER JOIN e.agent a WHERE e.id = ? AND a.orgId = ?") .setParameter(1, ipId).setParameter(2, orgId).getSingleResult(); try { DeniedMac d = (DeniedMac) em.createQuery("FROM DeniedMac d WHERE d.mac = ?").setParameter(1, mac) .getSingleResult(); throw new IllegalStateException("conflict with denied mac: " + d.getId()); } catch (NoResultException e) { } boolean isNew = false; // check if already registered AllowedMac allowedMac = null; for (AllowedMac a : ipEntry.getAllowedMacs()) { if (a.getMac().equals(mac)) allowedMac = a; } if (allowedMac == null) { allowedMac = new AllowedMac(); allowedMac.setIp(ipEntry); allowedMac.setMac(mac); allowedMac.setCreateDateTime(new Date()); isNew = true; } allowedMac.setBeginDateTime(from); allowedMac.setEndDateTime(to); if (isNew) em.persist(allowedMac); else em.merge(allowedMac); return allowedMac.getId(); } catch (NoResultException e) { throw new IllegalStateException("ip entry not found: " + ipId); } } @Transactional @Override public void disallowMacAddress(int orgId, int macId) { EntityManager em = entityManagerService.getEntityManager(); AllowedMac allowedMac = null; try { allowedMac = (AllowedMac) em.createQuery("FROM AllowedMac a WHERE a.id = ?").setParameter(1, macId) .getSingleResult(); if (allowedMac.getIp().getAgent().getOrgId() != orgId) throw new SecurityException("no permission to edit mac: " + macId); em.remove(allowedMac); } catch (NoResultException e) { throw new IllegalStateException("allowed mac not found: " + macId); } } @Transactional @Override public List<DeniedMac> getDenyMacAddresses(int orgId, int agentId) { EntityManager em = entityManagerService.getEntityManager(); Agent agent = (Agent) em.createQuery("FROM Agent a WHERE a.id = ?").setParameter(1, agentId).getSingleResult(); if (agent.getOrgId() != orgId) return null; return agent.getDeniedMac(); } @Transactional @Override public int denyMacAddress(int orgId, int agentId, String mac, Date from, Date to) { mac = mac.toUpperCase(); EntityManager em = entityManagerService.getEntityManager(); try { Agent agent = (Agent) em.createQuery("FROM Agent a WHERE a.id = ? AND a.orgId = ?") .setParameter(1, agentId).setParameter(2, orgId).getSingleResult(); boolean isNew = false; DeniedMac deniedMac = null; for (DeniedMac m : agent.getDeniedMac()) { if (m.getMac().equals(mac)) deniedMac = m; } if (deniedMac == null) { deniedMac = new DeniedMac(); deniedMac.setAgent(agent); deniedMac.setMac(mac); deniedMac.setCreateDateTime(new Date()); isNew = true; } deniedMac.setBeginDateTime(from); deniedMac.setEndDateTime(to); if (isNew) em.persist(deniedMac); else em.merge(deniedMac); return deniedMac.getId(); } catch (NoResultException e) { throw new IllegalStateException("agent not found: " + agentId); } } @Transactional @Override public void removeDenyMacAddress(int orgId, int macId) { EntityManager em = entityManagerService.getEntityManager(); DeniedMac deniedMac = null; try { deniedMac = (DeniedMac) em.createQuery("FROM DeniedMac d WHERE d.id = ?").setParameter(1, macId) .getSingleResult(); if (deniedMac.getAgent().getOrgId() != orgId) throw new SecurityException("no permission to edit mac: " + macId); em.remove(deniedMac); } catch (NoResultException e) { throw new IllegalStateException("denied mac not found: " + macId); } } @Override public void updateIpEntry(IpDetection detection) { queue.add(detection); } @Transactional @Override public List<IpEventLog> getLogs(LogQueryCondition condition) { int page = condition.getPage(); if (page < 1) throw new IllegalArgumentException("page number should be natural number"); EntityManager em = entityManagerService.getEntityManager(); int pageSize = condition.getPageSize(); int offset = (page - 1) * pageSize; CriteriaBuilder cb = em.getCriteriaBuilder(); CriteriaQuery<IpEventLog> cq = cb.createQuery(IpEventLog.class); Root<IpEventLog> root = cq.from(IpEventLog.class); cq.where(condition.getPredicate(cb, root)); cq.orderBy(cb.desc(root.get("id"))); List<IpEventLog> logs = em.createQuery(cq).setFirstResult(offset).setMaxResults(pageSize).getResultList(); return logs; } @Override public void run() { logger.info("kraken ipmanager: starting ip sync thread"); while (!doStop) { try { syncIpEntries(); Thread.sleep(3000); } catch (InterruptedException e) { } } } @SuppressWarnings("unchecked") @Transactional private void syncIpEntries() { EntityManager em = entityManagerService.getEntityManager(); List<IpDetection> l = new LinkedList<IpDetection>(); queue.drainTo(l); if (l.size() == 0) return; Map<String, Agent> agents = mapAgents((List<Agent>) em.createQuery("FROM Agent a").getResultList()); List<String> ipFilter = ipFilter(l); Map<IpEntryKey, IpEntry> entries = mapIpEntries(em.createQuery("FROM IpEntry e WHERE e.ip IN (:filter)") .setParameter("filter", ipFilter).getResultList()); List<String> macFilter = macFilter(l); Map<DetectedMacKey, DetectedMac> macs = mapDetectedMacs(em .createQuery("FROM DetectedMac d WHERE d.mac IN (:filter)").setParameter("filter", macFilter) .getResultList()); for (IpDetection d : l) { sync(em, agents, entries, macs, d); } } /* * Agent GUID to Agent map */ private Map<String, Agent> mapAgents(List<Agent> agents) { Map<String, Agent> m = new HashMap<String, Agent>(); for (Agent a : agents) m.put(a.getGuid(), a); return m; } private Map<IpEntryKey, IpEntry> mapIpEntries(List<IpEntry> entries) { Map<IpEntryKey, IpEntry> m = new HashMap<IpEntryKey, IpEntry>(); for (IpEntry e : entries) m.put(new IpEntryKey(e.getAgent().getId(), e.getIp()), e); return m; } private Map<DetectedMacKey, DetectedMac> mapDetectedMacs(List<DetectedMac> macs) { Map<DetectedMacKey, DetectedMac> m = new HashMap<DetectedMacKey, DetectedMac>(); for (DetectedMac d : macs) m.put(new DetectedMacKey(d.getAgent().getId(), d.getMac()), d); return m; } private List<String> ipFilter(List<IpDetection> l) { List<String> s = new ArrayList<String>(l.size()); for (IpDetection d : l) s.add(d.getIp().getHostAddress()); return s; } private List<String> macFilter(List<IpDetection> l) { List<String> s = new ArrayList<String>(l.size()); for (IpDetection d : l) s.add(d.getMac().toString()); return s; } private void sync(EntityManager em, Map<String, Agent> agents, Map<IpEntryKey, IpEntry> entries, Map<DetectedMacKey, DetectedMac> macs, IpDetection d) { Agent agent = agents.get(d.getAgentGuid()); if (agent == null) { logger.warn("kraken ipmanager: agent [{}] not found, discarded", d.getAgentGuid()); return; } // netbios/dhcp host check HostEntry host = syncHostEntry(em, d, agent); if (d.getIp() != null && !d.getIp().getHostAddress().equals("0.0.0.0")) syncIpEntry(em, entries, macs, d, agent, host); } private void syncIpEntry(EntityManager em, Map<IpEntryKey, IpEntry> entries, Map<DetectedMacKey, DetectedMac> macs, IpDetection d, Agent agent, HostEntry host) { IpEntry entry = null; logger.debug("kraken ipmanager: trace ip detection [{}]", d); // new ip? IpEntryKey key = new IpEntryKey(agent.getId(), d.getIp().getHostAddress()); if (!entries.containsKey(key)) { entry = new IpEntry(); entry.setIp(d.getIp().getHostAddress()); entry.setAgent(agent); entry.setFirstSeen(d.getDate()); entry.setLastSeen(d.getDate()); entry.setCurrentMac(d.getMac().toString()); if (host != null) entry.getHostEntries().add(host); em.persist(entry); entries.put(key, entry); generateLog(em, agent, Type.NewIpDetected, d); notifyNewIpDetected(d, agent); logger.trace("kraken ipmanager: new ip [{}] added", d); } else { entry = entries.get(key); if (host != null) entry.getHostEntries().add(host); // ip conflict? if (!entry.getCurrentMac().equals(d.getMac().toString())) { MacAddress originalMac = new MacAddress(entry.getCurrentMac()); entry.setCurrentMac(d.getMac().toString()); generateLog(em, agent, Type.IpConflict, d, originalMac.toString(), null); notifyIpConflict(d, agent, originalMac); } entry.setLastSeen(d.getDate()); em.merge(entry); } // update detected mac DetectedMacKey macKey = new DetectedMacKey(agent.getId(), d.getMac().toString()); DetectedMac detectedMac = macs.get(macKey); if (detectedMac == null) { detectedMac = newDetectedMac(d, agent, entry); em.persist(detectedMac); macs.put(macKey, detectedMac); generateLog(em, agent, Type.NewMacDetected, d); notifyNewMacDetected(d, agent); } else { // is ip changed? if (!detectedMac.getIp().getIp().equals(d.getIp().getHostAddress())) { DetectedMac changed = newDetectedMac(d, agent, entry); em.persist(changed); macs.put(macKey, changed); generateLog(em, agent, Type.IpChanged, d, null, detectedMac.getIp().getIp()); notifyIpChanged(d, agent, detectedMac.getIp().getIp()); } else { // not changed, just update last seen detectedMac.setLastSeen(d.getDate()); em.merge(detectedMac); } } } private DetectedMac newDetectedMac(IpDetection d, Agent agent, IpEntry entry) { DetectedMac detectedMac; detectedMac = new DetectedMac(); detectedMac.setAgent(agent); detectedMac.setFirstSeen(d.getDate()); detectedMac.setLastSeen(d.getDate()); detectedMac.setIp(entry); detectedMac.setMac(d.getMac().toString()); return detectedMac; } private HostEntry syncHostEntry(EntityManager em, IpDetection d, Agent agent) { if (d.getHostName() == null && d.getWorkGroup() == null) return null; HostEntry hostEntry = null; try { hostEntry = (HostEntry) em .createQuery("SELECT h FROM HostEntry h INNER JOIN h.hostMacs m WHERE h.agent = ? AND m.mac = ?") .setParameter(1, agent).setParameter(2, d.getMac().toString()).getSingleResult(); setHostEntry(d, hostEntry); em.merge(hostEntry); } catch (NoResultException e) { hostEntry = new HostEntry(); hostEntry.setAgent(agent); hostEntry.setFirstSeen(d.getDate()); setHostEntry(d, hostEntry); em.persist(hostEntry); HostNic nic = new HostNic(); nic.setHost(hostEntry); nic.setFirstSeen(d.getDate()); nic.setLastSeen(d.getDate()); nic.setMac(d.getMac().toString()); em.persist(nic); } return hostEntry; } private void setHostEntry(IpDetection d, HostEntry hostEntry) { hostEntry.setLastSeen(d.getDate()); if (d.getHostName() != null) hostEntry.setName(d.getHostName()); if (d.getWorkGroup() != null) hostEntry.setWorkGroup(d.getWorkGroup()); if (d.getCategory() != null) hostEntry.setCategory(d.getCategory()); if (d.getVendor() != null) hostEntry.setVendor(d.getVendor()); } private static class DetectedMacKey { private int agentId; private String mac; public DetectedMacKey(int agentId, String mac) { this.agentId = agentId; this.mac = mac; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + agentId; result = prime * result + ((mac == null) ? 0 : mac.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; DetectedMacKey other = (DetectedMacKey) obj; if (agentId != other.agentId) return false; if (mac == null) { if (other.mac != null) return false; } else if (!mac.equals(other.mac)) return false; return true; } } private static class IpEntryKey { private int agentId; private String ip; public IpEntryKey(int agentId, String ip) { this.agentId = agentId; this.ip = ip; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + agentId; result = prime * result + ((ip == null) ? 0 : ip.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; IpEntryKey other = (IpEntryKey) obj; if (agentId != other.agentId) return false; if (ip == null) { if (other.ip != null) return false; } else if (!ip.equals(other.ip)) return false; return true; } } private void generateLog(EntityManager em, Agent agent, Type type, IpDetection d) { generateLog(em, agent, type, d, null, null); } private void generateLog(EntityManager em, Agent agent, Type type, IpDetection d, String oldMac, String oldIp) { IpEventLog log = new IpEventLog(); log.setType(type.getCode()); log.setAgent(agent); log.setDate(d.getDate()); if (oldIp != null) { log.setIp1(oldIp); log.setIp2(d.getIp().getHostAddress()); } else { log.setIp1(d.getIp().getHostAddress()); } if (oldMac != null) { log.setMac1(oldMac); log.setMac2(d.getMac().toString()); } else { log.setMac1(d.getMac().toString()); } log.setOrgId(agent.getOrgId()); em.persist(log); } private void notifyIpConflict(IpDetection d, Agent agent, MacAddress originalMac) { // fire callbacks for (IpEventListener callback : callbacks) { try { callback.onIpConflict(agent, d.getIp(), originalMac, d.getMac()); } catch (Exception e) { logger.warn("kraken ipmanager: event callback should not throw any exception", e); } } } private void notifyNewIpDetected(IpDetection d, Agent agent) { // fire callbacks for (IpEventListener callback : callbacks) { try { callback.onNewIpDetected(agent, d.getIp(), d.getMac()); } catch (Exception e) { logger.warn("kraken ipmanager: event callback should not throw any exception", e); } } } private void notifyNewMacDetected(IpDetection d, Agent agent) { // fire callbacks for (IpEventListener callback : callbacks) { try { callback.onNewMacDetected(agent, d.getIp(), d.getMac()); } catch (Exception e) { logger.warn("kraken ipmanager: event callback should not throw any exception", e); } } } private void notifyIpChanged(IpDetection d, Agent agent, String oldIp) { // fire ip changed callbacks for (IpEventListener callback : callbacks) { try { callback.onIpChanged(agent, InetAddress.getByName(oldIp), d.getIp(), d.getMac()); } catch (Exception e) { logger.warn("kraken ipmanager: event callback should not throw any exception", e); } } } @Override public void addListener(IpEventListener callback) { callbacks.add(callback); } @Override public void removeListener(IpEventListener callback) { callbacks.remove(callback); } }