stun/src/main/java/com/github/freeacs/stun/ActiveDeviceDetection.java
package com.github.freeacs.stun;
import com.github.freeacs.common.scheduler.TaskDefaultImpl;
import com.github.freeacs.common.util.TimestampMap;
import com.github.freeacs.dbi.ACSUnit;
import com.github.freeacs.dbi.DBI;
import com.github.freeacs.dbi.Heartbeat;
import com.github.freeacs.dbi.Syslog;
import com.github.freeacs.dbi.SyslogConstants;
import com.github.freeacs.dbi.SyslogEntry;
import com.github.freeacs.dbi.SyslogFilter;
import com.github.freeacs.dbi.Unit;
import com.github.freeacs.dbi.util.SyslogClient;
import de.javawi.jstun.StunServer;
import java.sql.SQLException;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import javax.sql.DataSource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ActiveDeviceDetection extends TaskDefaultImpl {
private static final Logger logger = LoggerFactory.getLogger(ActiveDeviceDetection.class);
private final DataSource xapsCp;
private final DBI dbi;
private final TimestampMap activeDevicesLogged = new TimestampMap();
public ActiveDeviceDetection(DataSource xapsCp, DBI dbi, String taskName) {
super(taskName);
this.xapsCp = xapsCp;
this.dbi = dbi;
}
private void logActiveDevices(TimestampMap activeDevices, TimestampMap sentSyslogMap)
throws SQLException {
// Process 1/60 of all active devices each time this method is called
// Note that process 1/60 of the activeDevices map will be too much, since
// some of the units will be more than 5 minutes old - and we cannot log
// as "actually active" if the device timestamp is more than 5 minutes old.
// Hence a portion of the activeDevices map (perhaps 1/10?) will be older
// than 5 minutes, and then those 1/60 will actually process a larger share
// than strictly required. This is not a problem - the most important
// thing is to process all devices within an hour and to distribute the
// load over time. Worst case scenario is that no units will be processed
// at the end of a 60-minute cycle.
int unitsToProcess = activeDevices.size() / 60;
long fiveMinAgo = getThisLaunchTms() - 5 * 60000;
long oneHourAgo = getThisLaunchTms() - 60 * 60000;
ACSUnit acsUnit = new ACSUnit(xapsCp, dbi.getAcs(), dbi.getSyslog());
// this will force units which haven't been processed the last hour to
// be processed again.
Map<String, Long> oldDevices = sentSyslogMap.removeOld(oneHourAgo);
logger.info(
"ActiveDeviceDetection: Have removed "
+ oldDevices.size()
+ " devices from sentSyslog map - should be approx 1/60 of activeDevices.size() ("
+ activeDevices.size()
+ ")");
int processCount = 0;
int loggedCount = 0;
for (Entry<String, Long> entry : activeDevices.getMap().entrySet()) {
String address = entry.getKey();
if (processCount > unitsToProcess) {
break;
}
if (entry.getValue() > fiveMinAgo && sentSyslogMap.get(address) == null) {
processCount++;
sentSyslogMap.put(address, getThisLaunchTms());
Unit unit = acsUnit.getUnitByValue(address, null, null);
if (unit != null) {
loggedCount++;
SyslogClient.info(
unit.getId(),
"StunMsg/TR-111: Requests from " + address + " within last 5 minutes",
16,
null,
null);
} else {
logger.info(
"ActiveDeviceDetection: Stun request from "
+ address
+ ", but the address was not recorded in Fusion - consider adding A-flag to UDPConnectionRequestAddress in all unittypes");
}
}
}
logger.info(
"ActiveDeviceDetection: Processed "
+ processCount
+ " active device syslog messages to the syslog server. Sent "
+ loggedCount
+ " syslog messages. SentSyslogMap.size() = "
+ sentSyslogMap.size()
+ ", ActiveDevices.size() = "
+ activeDevices.size());
}
private void logInactiveDevices(TimestampMap activeDevices) throws SQLException {
long tooOldTms = getThisLaunchTms() - 3600 * 1000;
logger.info(
"Will check for inactive STUN clients (map size before check: "
+ activeDevices.size()
+ ")");
Map<String, Long> tooOldMap = activeDevices.removeOldSync(tooOldTms);
for (Entry<String, Long> entry : tooOldMap.entrySet()) {
String address = entry.getKey();
ACSUnit acsUnit = new ACSUnit(xapsCp, dbi.getAcs(), dbi.getSyslog());
Unit unit = acsUnit.getUnitByValue(address, null, null);
if (unit != null) {
Syslog syslog = dbi.getSyslog();
SyslogFilter sf = new SyslogFilter();
sf.setCollectorTmsStart(new Date(tooOldTms)); // look for syslog newer than 1 hour
sf.setUnitId(unit.getId());
boolean active = false;
List<SyslogEntry> entries = syslog.read(sf, dbi.getAcs());
for (SyslogEntry sentry : entries) {
String c = sentry.getContent();
if (sentry.getFacility() < SyslogConstants.FACILITY_SHELL
&& !c.contains(Heartbeat.MISSING_HEARTBEAT_ID)
&& !c.startsWith("StunMsg/TR-111")) {
logger.info(
"ActivceDeviceDetection: Found syslog activity for unit "
+ unit.getId()
+ " at "
+ sentry.getCollectorTimestamp()
+ " : "
+ sentry.getContent());
active = true;
break;
}
}
if (active) {
logger.info(
"ActiveDeviceDection: No STUN request from "
+ address
+ " (unit: "
+ unit.getId()
+ ") since "
+ new Date(tooOldTms));
SyslogClient.info(
unit.getId(),
"StunMsg/TR-111: No request from "
+ address
+ " since "
+ new Date(tooOldTms)
+ " - but device has been active since then",
dbi.getSyslog());
} else {
logger.info(
"ActiveDeviceDection: No STUN request from "
+ address
+ " (unit: "
+ unit.getId()
+ ") since "
+ new Date(tooOldTms)
+ " - but the device may not be active");
}
} else {
logger.info(
"ActiveDeviceDection: No STUN request from "
+ address
+ " for more than 60 minutes (the device may have changed IP).");
}
}
logger.info(
"ActiveDeviceDection: Have removed "
+ tooOldMap.size()
+ " devices from active devices map");
}
@Override
public void runImpl() throws Throwable {
TimestampMap activeDevices = StunServer.getActiveStunClients();
logInactiveDevices(
activeDevices); // will clean out old and inactive devices from activeDevices map (and log
// new inactive devices)
logActiveDevices(
activeDevices,
activeDevicesLogged); // will update list of devices logged to syslog (and log new ones)
}
@Override
public Logger getLogger() {
return logger;
}
}