Thread Safe implementation for In-Memory cache - java

import java.io.IOException;
import java.lang.ref.SoftReference;
import java.net.URI;
import java.security.cert.CRLException;
import java.security.cert.CertificateException;
import java.security.cert.X509CRL;
import java.security.cert.X509Certificate;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import javax.naming.NamingException;
import org.joda.time.DateTime;
import org.kp.oppr.esb.logger.Logger;
import org.springframework.beans.factory.annotation.Autowired;
public class CachedCrlRepository {
private static final Logger LOGGER = new Logger("CachedCrlRepository");
private final Map<URI, SoftReference<X509CRL>> crlCache = Collections
.synchronizedMap(new HashMap<URI, SoftReference<X509CRL>>());;
private static int DEFAULT_CACHE_AGING_HOURS;
#Autowired
private DgtlSgntrValidator validator;
#Autowired
private CrlRepository crlRepository;
public X509CRL findCrl(URI crlUri, X509Certificate issuerCertificate,
Date validationDate) throws DigitalValdiationException,
CertificateException, CRLException, IOException, NamingException {
SoftReference<X509CRL> crlRef = this.crlCache.get(crlUri);
if (null == crlRef) {
LOGGER.info("Key CRL URI : " + crlUri + " not found in the cache " );
return refreshCrl(crlUri, issuerCertificate, validationDate);
}
X509CRL crl = crlRef.get();
if (null == crl) {
LOGGER.info("CRL Entry garbage collected: " + crlUri);
return refreshCrl(crlUri, issuerCertificate, validationDate);
}
if (validationDate.after(crl.getNextUpdate())) {
LOGGER.info("CRL URI no longer valid: " + crlUri);
LOGGER.info("CRL validation date: " + validationDate + " is after CRL next update date: " + crl.getNextUpdate());
return refreshCrl(crlUri, issuerCertificate, validationDate);
}
Date thisUpdate = crl.getThisUpdate();
LOGGER.info("This update " + thisUpdate);
/*
* The PKI the nextUpdate CRL extension indicates 7 days. The
* actual CRL refresh rate is every 3 hours. So it's a bit dangerous to
* only base the CRL cache refresh strategy on the nextUpdate field as
* indicated by the CRL.
*/
DateTime cacheMaturityDateTime = new DateTime(thisUpdate)
.plusHours(DEFAULT_CACHE_AGING_HOURS);
LOGGER.info("Cache maturity Date Time " + cacheMaturityDateTime);
if (validationDate.after(cacheMaturityDateTime.toDate())) {
LOGGER.info("Validation date: " + validationDate + " is after cache maturity date: " + cacheMaturityDateTime.toDate());
return refreshCrl(crlUri, issuerCertificate, validationDate);
}
LOGGER.info("using cached CRL: " + crlUri);
return crl;
}
public static int getDEFAULT_CACHE_AGING_HOURS() {
return DEFAULT_CACHE_AGING_HOURS;
}
public static void setDEFAULT_CACHE_AGING_HOURS(int dEFAULT_CACHE_AGING_HOURS) {
DEFAULT_CACHE_AGING_HOURS = dEFAULT_CACHE_AGING_HOURS;
}
private X509CRL refreshCrl(URI crlUri, X509Certificate issuerCertificate,
Date validationDate) throws DigitalValdiationException,
CertificateException, CRLException, IOException, NamingException {
X509CRL crl = crlRepository.downloadCRL(crlUri.toString());
this.crlCache.put(crlUri, new SoftReference<X509CRL>(crl));
return crl;
}
}
I have this class CachedCrlrepository that stores CRL list from particular provider. I want to know if my implementation is thread safe or I am missing something over here. The cache is for a web service, so it is multi-threaded.
My doubt in for this particular method
private X509CRL refreshCrl(URI crlUri, X509Certificate issuerCertificate,
Date validationDate) throws DigitalValdiationException,
CertificateException, CRLException, IOException, NamingException {
X509CRL crl = crlRepository.downloadCRL(crlUri.toString());
this.crlCache.put(crlUri, new SoftReference<X509CRL>(crl));
return crl;
}
I think this particular line need to be synchronized
this.crlCache.put(crlUri, new SoftReference<X509CRL>(crl));
synchronized(this)
{
this.crlCache.put(crlUri, new SoftReference<X509CRL>(crl));
}
Another issue which I see is that after a GC is run the cache still have that entry in the memory. It never execute these line of code
if (null == crl) {
LOGGER.info("CRL Entry garbage collected: " + crlUri);
return refreshCrl(crlUri, issuerCertificate, validationDate);
}

Generally you should not use a synchronized Map in cases where you are expecting heavy traffic and high concurrent access on your object which in this case is crlCache. For each and every read or write threads will wait behind another and in heavy load, your thread count will go high and eventually your server will crash. You can look into ConcurrentHashMap. which is designed to work efficiently in such scenarios.
Your second point:
synchronized(this)
{
this.crlCache.put(crlUri, new SoftReference<X509CRL>(crl));
}
is not at all required with current code as put method is already synchronized.
For minimal changes replace
private final Map<URI, SoftReference<X509CRL>> crlCache = Collections
.synchronizedMap(new HashMap<URI, SoftReference<X509CRL>>());;
with
private final ConcurrentHashMap<URI, SoftReference<X509CRL>> crlCache = new ConcurrentHashMap<URI, SoftReference<X509CRL>>();
Finally, using SoftReference is good but there are better options. Guava from google is a very robust and efficient cache builder.

Related

iText Verify Signature with checking CRL

I am setting up a verifier which makes it possible to check the validity of signature.
The signature I do is based on DSS level LT so revocation checking is built into the document.
The problem that I encounter now is at the level of the verifier that I developed in iText. It allows the verification of the validity of the signature but of the information of the revocation. IText according to my research allows to verify this information in the signature itself based on: pkcs7.getCrl().
However, the DSS signature incorporates the revocation information into the dictionaries.
Below is the code I use to verify the signature:
import com.itextpdf.text.pdf.AcroFields;
import com.itextpdf.text.pdf.PdfDictionary;
import com.itextpdf.text.pdf.PdfName;
import com.itextpdf.text.pdf.PdfReader;
import com.itextpdf.text.pdf.PdfString;
import com.itextpdf.text.pdf.security.PdfPKCS7;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.GeneralSecurityException;
import java.security.Principal;
import java.security.cert.X509Certificate;
import java.util.Calendar;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.security.Security;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
public class TestCheck {
public static String pdf_file = "CURRENT_SIGNATURE.pdf";
public static final boolean verifySignature(PdfReader pdfReader)
throws GeneralSecurityException, IOException {
boolean valid = false;
AcroFields acroFields = pdfReader.getAcroFields();
PdfDictionary sigDict = acroFields.getSignatureDictionary("Signature1");
System.out.println(sigDict);
PdfString contents = sigDict.getAsString(PdfName.CONTENTS);
List<String> signatureNames = acroFields.getSignatureNames();
if (!signatureNames.isEmpty()) {
for (String name : signatureNames) {
// if (acroFields.signatureCoversWholeDocument(name)) {
PdfPKCS7 pkcs7 = acroFields.verifySignature(name);
valid = pkcs7.verify();
String reason = pkcs7.getReason();
Calendar signedAt = pkcs7.getSignDate();
X509Certificate signingCertificate = pkcs7.getSigningCertificate();
Principal issuerDN = signingCertificate.getIssuerDN();
Principal subjectDN = signingCertificate.getSubjectDN();
System.out.println("valid = "+valid);
//System.out.println("date = "+signedAt.getTime());
////System.out.println("reason = "+reason);
//System.out.println("issuer = "+issuerDN);
//System.out.println("subject = "+subjectDN);
System.out.println("CRL : " + pkcs7.getCRLs());
break;
}
// }
}
return valid;
}
public static void main(String[] args) throws Exception {
BouncyCastleProvider provider = new BouncyCastleProvider();
Security.addProvider(provider);
InputStream is = new FileInputStream(new File(pdf_file));
PdfReader reader = new PdfReader(is);
boolean ok = verifySignature(reader);
System.out.println("Ver : "+ ok);
}
}
Initially I wanted to simply point to the LtvVerifier class provided both by iText 5 and iText 7. Testing with that class, though, it turned out to not be applicable to the current PAdES BASELINE profiles but instead has been designed for an older PAdES-LTV profile (see ETSI TS 102 778-4 section 4 "Profile for PAdES-LTV").
If I understand your question correctly, though, you already know how to evaluate CRLs and OCSP responses. Thus, it would suffice if you learned how to extract the revocation information from the DSS dictionaries.
Your example code apparently uses iText 5.x, so I used the current iText 5.5.14-SNAPSHOT. A bit older versions should be usable with the same code, too.
PdfReader pdfReader = new PdfReader(...);
PdfDictionary dss = pdfReader.getCatalog().getAsDict(PdfName.DSS);
if (dss == null)
System.out.println("No DSS in PDF");
else {
PdfArray crlarray = dss.getAsArray(PdfName.CRLS);
if (crlarray == null || crlarray.size() == 0)
System.out.println("No CRLs in DSS");
else {
System.out.println("CRLs:");
CertificateFactory cf = CertificateFactory.getInstance("X.509");
for (int i = 0; i < crlarray.size(); i++) {
PRStream stream = (PRStream) crlarray.getAsStream(i);
X509CRL crl = (X509CRL)cf.generateCRL(new ByteArrayInputStream(PdfReader.getStreamBytes(stream)));
System.out.printf(" '%s' update %s\n", crl.getIssuerX500Principal(), crl.getThisUpdate());
}
}
PdfArray ocsparray = dss.getAsArray(PdfName.OCSPS);
if (ocsparray == null || ocsparray.size() == 0)
System.out.println("\nNo OCSP responses in DSS");
else {
System.out.println("\nOCSP Responses:");
for (int i = 0; i < ocsparray.size(); i++) {
PRStream stream = (PRStream) ocsparray.getAsStream(i);
OCSPResp ocspResponse = new OCSPResp(PdfReader.getStreamBytes(stream));
if (ocspResponse.getStatus() == 0) {
try {
BasicOCSPResp basicOCSPResp = (BasicOCSPResp) ocspResponse.getResponseObject();
System.out.printf(" '%s' update %s\n", basicOCSPResp.getResponderId(), basicOCSPResp.getProducedAt());
} catch (OCSPException e) {
throw new GeneralSecurityException(e);
}
}
}
}
}
(VerifyLtv test testExtractRevocationInformationCURRENT_SIGNATURE)
Instead of printing information to System.out you can of course collect the CRLs and OCSP responses and process them as you used to.
Also, you can of course check both the revocation data you retrieve from the PdfPKCS7 object and the data from the DSS. Adobe Acrobat also uses both during verification.

Amazon SQS call lambda function

I am trying to trigger function thru amazon sqs trigger. The trigger is working fine but, the message is not passed into the my function.
Here is my lambda function
import java.text.SimpleDateFormat;
import java.util.Calendar;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.LambdaLogger;
import com.amazonaws.services.lambda.runtime.RequestHandler;
public class x implements RequestHandler<RequestClass, ResponseClass> {
private LambdaLogger logger;
public void log(String message) {
Calendar cal = Calendar.getInstance();
SimpleDateFormat sdf = new SimpleDateFormat("MM-dd-yyyy HH:mm:ss");
logger.log(sdf.format(cal.getTime()) + " " + message+"\n");
}
public ResponseClass handleRequest(RequestClass request, Context context) {
this.logger = context.getLogger();
log("Request " + request);
if (request == null || (request.getFilename() == null && request.getRecords() == null)) {
log("No file was passed in");
throw new RuntimeException("No file was passed in");
}
return new ResponseClass(null);
}
}
And request class is https://pastebin.com/Q1G6bnrA
The records are always null when I see logs.
Have you taken care of the execution role permissions of the Lambda?
From here:
Execution Role Permissions
Lambda needs the following permissions to manage messages in your Amazon SQS queue. Add them to your function's execution role.
sqs:ReceiveMessage
sqs:DeleteMessage
sqs:GetQueueAttributes
The following code is working fine for me:
package au.com.redbarn.aws.lambda2lambda_via_sqs;
import java.util.List;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import com.amazonaws.services.lambda.runtime.events.SQSEvent;
import com.amazonaws.services.lambda.runtime.events.SQSEvent.SQSMessage;
import lombok.extern.log4j.Log4j2;
#Log4j2
public class SQSConsumerLambda implements RequestHandler<SQSEvent, String> {
#Override
public String handleRequest(SQSEvent input, Context context) {
log.info("message received");
List<SQSMessage> records = input.getRecords();
for (SQSMessage record : records) {
log.info(record.getBody());
}
return "Ok";
}
}
Maybe try using SQSEvent instead of your own RequestClass.

How to add "text/plain" MIME type to DataHandler

I have been struggling with getting this test to work for awhile, the relevant code executes fine in production my assumption is that it has some additional configuration, lots of searching seems to be related specifically to email handling and additional libraries, I don't want to include anything else, what am I missing to link DataHandler to a relevant way of handling "text/plain" ?
Expected result: DataHandler allows me to stream the input "Value" back into a result.
Reproduce issue with this code:
import java.io.IOException;
import java.io.InputStream;
import javax.activation.CommandInfo;
import javax.activation.CommandMap;
import javax.activation.DataHandler;
import org.apache.commons.io.IOUtils;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
public class DataHandlerTest {
#Before
public void setUp() throws Exception {
}
#After
public void tearDown() throws Exception {
}
#Test
public void test() throws IOException {
printDefaultCommandMap();
DataHandler dh = new DataHandler("Value", "text/plain");
System.out.println("DataHandler commands:");
printDataHandlerCommands(dh);
dh.setCommandMap(CommandMap.getDefaultCommandMap());
System.out.println("DataHandler commands:");
printDataHandlerCommands(dh);
final InputStream in = dh.getInputStream();
String result = new String(IOUtils.toByteArray(in));
System.out.println("Returned String: " + result);
}
private void printDataHandlerCommands(DataHandler dh) {
CommandInfo[] infos = dh.getAllCommands();
printCommands(infos);
}
private void printDefaultCommandMap() {
CommandMap currentMap = CommandMap.getDefaultCommandMap();
String[] mimeTypes = currentMap.getMimeTypes();
System.out.println("Found " + mimeTypes.length + " MIME types.");
for (String mimeType : mimeTypes) {
System.out.println("Commands for: " + mimeType);
printCommands(currentMap.getAllCommands(mimeType));
}
}
private void printCommands(CommandInfo[] infos) {
for (CommandInfo info : infos) {
System.out.println(" Command Class: " +info.getCommandClass());
System.out.println(" Command Name: " + info.getCommandName());
}
}
}
Exception:
javax.activation.UnsupportedDataTypeException: no object DCH for MIME
type text/plain at
javax.activation.DataHandler.getInputStream(DataHandler.java:249)
Help much appreciated, I hope this is a well formed question!
========================
Update 25th February
I have found if i know I stored a String in DataHandler, then I can cast the result to String and return the object that was stored, example:
#Test
public void testGetWithoutStream() throws IOException {
String inputString = "Value";
DataHandler dh = new DataHandler(inputString, "text/plain");
String rawResult = (String) dh.getContent();
assertEquals(inputString, rawResult);
}
But the code under test uses an InputStream, so my 'real' tests still fail when executed locally.
Continuing my investigation and still hoping for someone's assistance/guidance on this one...
Answering my own question for anyone's future reference.
All credit goes to: https://community.oracle.com/thread/1675030?start=0
The principle here is that you need to provide DataHandler a factory that contains a DataContentHandler that will behave as you would like it to for your MIME type, setting this is via a static method that seems to affect all DataHandler instances.
I declared a new class (SystemDataHandlerConfigurator), which has a single public method that creates my factory and provides it the static DataHandler.setDataContentHandlerFactory() function.
My tests now work correctly if I do this before they run:
SystemDataHandlerConfigurator configurator = new SystemDataHandlerConfigurator();
configurator.setupCustomDataContentHandlers();
SystemDataHandlerConfigurator
import java.io.IOException;
import javax.activation.*;
public class SystemDataHandlerConfigurator {
public void setupCustomDataContentHandlers() {
DataHandler.setDataContentHandlerFactory(new CustomDCHFactory());
}
private class CustomDCHFactory implements DataContentHandlerFactory {
#Override
public DataContentHandler createDataContentHandler(String mimeType) {
return new BinaryDataHandler();
}
}
private class BinaryDataHandler implements DataContentHandler {
/** Creates a new instance of BinaryDataHandler */
public BinaryDataHandler() {
}
/** This is the key, it just returns the data uninterpreted. */
public Object getContent(javax.activation.DataSource dataSource) throws java.io.IOException {
return dataSource.getInputStream();
}
public Object getTransferData(java.awt.datatransfer.DataFlavor dataFlavor,
javax.activation.DataSource dataSource)
throws java.awt.datatransfer.UnsupportedFlavorException,
java.io.IOException {
return null;
}
public java.awt.datatransfer.DataFlavor[] getTransferDataFlavors() {
return new java.awt.datatransfer.DataFlavor[0];
}
public void writeTo(Object obj, String mimeType, java.io.OutputStream outputStream)
throws java.io.IOException {
if (mimeType == "text/plain") {
byte[] stringByte = (byte[]) ((String) obj).getBytes("UTF-8");
outputStream.write(stringByte);
}
else {
throw new IOException("Unsupported Data Type: " + mimeType);
}
}
}
}

Drools- how to find out which all rules were matched?

I've one .DRL file which has say 10 rules. Once I insert a fact, some rules may be matched- how do I find out which rules were matched programmatically?
Note that this answer is valid for versions of Drools up to 5.x. If you have moved on to 6 or above, then take a look at the modified answer from #melchoir55. I haven't tested it myself, but I'll trust that it works.
To keep track of rule activations, you can use an AgendaEventListener. Below is an example, as found here:
https://github.com/gratiartis/sctrcd-payment-validation-web/blob/master/src/main/java/com/sctrcd/drools/util/TrackingAgendaEventListener.java
You just need to create such a listener and attach it to the session like so:
ksession = kbase.newStatefulKnowledgeSession();
AgendaEventListener agendaEventListener = new TrackingAgendaEventListener();
ksession.addEventListener(agendaEventListener);
//...
ksession.fireAllRules();
//...
List<Activation> activations = agendaEventListener.getActivationList();
Note that there is also WorkingMemoryEventListener which enables you to do the same with tracking insertions, updates and retractions of facts.
Code for a tracking & logging AgendaEventListener:
package com.sctrcd.drools.util;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.drools.definition.rule.Rule;
import org.drools.event.rule.DefaultAgendaEventListener;
import org.drools.event.rule.AfterActivationFiredEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A listener that will track all rule firings in a session.
*
* #author Stephen Masters
*/
public class TrackingAgendaEventListener extends DefaultAgendaEventListener {
private static Logger log = LoggerFactory.getLogger(TrackingAgendaEventListener.class);
private List<Activation> activationList = new ArrayList<Activation>();
#Override
public void afterActivationFired(AfterActivationFiredEvent event) {
Rule rule = event.getActivation().getRule();
String ruleName = rule.getName();
Map<String, Object> ruleMetaDataMap = rule.getMetaData();
activationList.add(new Activation(ruleName));
StringBuilder sb = new StringBuilder("Rule fired: " + ruleName);
if (ruleMetaDataMap.size() > 0) {
sb.append("\n With [" + ruleMetaDataMap.size() + "] meta-data:");
for (String key : ruleMetaDataMap.keySet()) {
sb.append("\n key=" + key + ", value="
+ ruleMetaDataMap.get(key));
}
}
log.debug(sb.toString());
}
public boolean isRuleFired(String ruleName) {
for (Activation a : activationList) {
if (a.getRuleName().equals(ruleName)) {
return true;
}
}
return false;
}
public void reset() {
activationList.clear();
}
public final List<Activation> getActivationList() {
return activationList;
}
public String activationsToString() {
if (activationList.size() == 0) {
return "No activations occurred.";
} else {
StringBuilder sb = new StringBuilder("Activations: ");
for (Activation activation : activationList) {
sb.append("\n rule: ").append(activation.getRuleName());
}
return sb.toString();
}
}
}
Steve's answer is solid, but the major changes brought in drools 6 make the code obsolete. I am posting below a rewrite of Steve's code which takes into account the new api:
package your.preferred.package;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.drools.core.event.DefaultAgendaEventListener;
import org.kie.api.definition.rule.Rule;
import org.kie.api.event.rule.AfterMatchFiredEvent;
import org.kie.api.runtime.rule.Match;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A listener that will track all rule firings in a session.
*
* #author Stephen Masters, Isaac Martin
*/
public class TrackingAgendaEventListener extends DefaultAgendaEventListener {
private static Logger log = LoggerFactory.getLogger(TrackingAgendaEventListener.class);
private List<Match> matchList = new ArrayList<Match>();
#Override
public void afterMatchFired(AfterMatchFiredEvent event) {
Rule rule = event.getMatch().getRule();
String ruleName = rule.getName();
Map<String, Object> ruleMetaDataMap = rule.getMetaData();
matchList.add(event.getMatch());
StringBuilder sb = new StringBuilder("Rule fired: " + ruleName);
if (ruleMetaDataMap.size() > 0) {
sb.append("\n With [" + ruleMetaDataMap.size() + "] meta-data:");
for (String key : ruleMetaDataMap.keySet()) {
sb.append("\n key=" + key + ", value="
+ ruleMetaDataMap.get(key));
}
}
log.debug(sb.toString());
}
public boolean isRuleFired(String ruleName) {
for (Match a : matchList) {
if (a.getRule().getName().equals(ruleName)) {
return true;
}
}
return false;
}
public void reset() {
matchList.clear();
}
public final List<Match> getMatchList() {
return matchList;
}
public String matchsToString() {
if (matchList.size() == 0) {
return "No matchs occurred.";
} else {
StringBuilder sb = new StringBuilder("Matchs: ");
for (Match match : matchList) {
sb.append("\n rule: ").append(match.getRule().getName());
}
return sb.toString();
}
}
}
You can use a static logger factory which will log with your favorite logger the actions from your DRL file.
For instance:
import org.drools.runtime.rule.RuleContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public final class DRLLogger {
private DRLLogger() {
}
protected static Logger getLogger(final RuleContext drools) {
final String category = drools.getRule().getPackageName() + "." + drools.getRule().getName();
final Logger logger = LoggerFactory.getLogger(category);
return logger;
}
public static void info(final RuleContext drools, final String message, final Object... parameters) {
final Logger logger = getLogger(drools);
logger.info(message, parameters);
}
public static void debug(final RuleContext drools, final String message, final Object... parameters) {
final Logger logger = getLogger(drools);
logger.debug(message, parameters);
}
public static void error(final RuleContext drools, final String message, final Object... parameters) {
final Logger logger = getLogger(drools);
logger.error(message, parameters);
}
}
Then from your DRL file:
import function com.mycompany.DRLLogger.*
rule "myrule"
when
$fact: Fact()
then
info(drools, "Fact:{}", $fact);
end
Change the dialect to JAVA in DRL file.
Insert a HashMap from the java file to DRL file (using Drools session concept),
which should contain the rule name as key and boolean value as result.
Follow this link to know how to insert Map to the DRL file.
You can now find which rule exactly matched.
Hope this helps :)
You can print info about rule executed from DRL file itself using RuleContext:drools
System.out.println(drools.getRule().getName())

Java DNS cache viewer

Is there a way to view/dump DNS cached used by java.net api?
Here is a script to print the positive and negative DNS address cache.
import java.lang.reflect.Field;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
public class DNSCache {
public static void main(String[] args) throws Exception {
InetAddress.getByName("stackoverflow.com");
InetAddress.getByName("www.google.com");
InetAddress.getByName("www.yahoo.com");
InetAddress.getByName("www.example.com");
try {
InetAddress.getByName("nowhere.example.com");
} catch (UnknownHostException e) {
}
String addressCache = "addressCache";
System.out.println(addressCache);
printDNSCache(addressCache);
String negativeCache = "negativeCache";
System.out.println(negativeCache);
printDNSCache(negativeCache);
}
private static void printDNSCache(String cacheName) throws Exception {
Class<InetAddress> klass = InetAddress.class;
Field acf = klass.getDeclaredField(cacheName);
acf.setAccessible(true);
Object addressCache = acf.get(null);
Class cacheKlass = addressCache.getClass();
Field cf = cacheKlass.getDeclaredField("cache");
cf.setAccessible(true);
Map<String, Object> cache = (Map<String, Object>) cf.get(addressCache);
for (Map.Entry<String, Object> hi : cache.entrySet()) {
Object cacheEntry = hi.getValue();
Class cacheEntryKlass = cacheEntry.getClass();
Field expf = cacheEntryKlass.getDeclaredField("expiration");
expf.setAccessible(true);
long expires = (Long) expf.get(cacheEntry);
Field af = cacheEntryKlass.getDeclaredField("address");
af.setAccessible(true);
InetAddress[] addresses = (InetAddress[]) af.get(cacheEntry);
List<String> ads = new ArrayList<String>(addresses.length);
for (InetAddress address : addresses) {
ads.add(address.getHostAddress());
}
System.out.println(hi.getKey() + " "+new Date(expires) +" " +ads);
}
}
}
The java.net.InetAddress uses caching of successful and unsuccessful host name resolutions.
From its javadoc:
The InetAddress class has a cache to
store successful as well as
unsuccessful host name resolutions.
By default, when a security manager is
installed, in order to protect against
DNS spoofing attacks, the result of
positive host name resolutions are
cached forever. When a security
manager is not installed, the default
behavior is to cache entries for a
finite (implementation dependent)
period of time. The result of
unsuccessful host name resolution is
cached for a very short period of time
(10 seconds) to improve performance.
If the default behavior is not
desired, then a Java security property
can be set to a different Time-to-live
(TTL) value for positive caching.
Likewise, a system admin can configure
a different negative caching TTL value
when needed.
Two Java security properties control
the TTL values used for positive and
negative host name resolution caching:
networkaddress.cache.ttl
Indicates the caching policy for
successful name lookups from the name
service. The value is specified as as
integer to indicate the number of
seconds to cache the successful
lookup. The default setting is to
cache for an implementation specific
period of time.
A value of -1 indicates "cache
forever".
networkaddress.cache.negative.ttl (default: 10)
Indicates the caching
policy for un-successful name lookups
from the name service. The value is
specified as as integer to indicate
the number of seconds to cache the
failure for un-successful lookups.
A value of 0 indicates "never cache".
A value of -1 indicates "cache
forever".
If what you have in mind is dumping the caches (of type java.net.InetAddress$Cache) used by java.net.InetAddress , they are internal implementation details and thus private:
/*
* Cached addresses - our own litle nis, not!
*/
private static Cache addressCache = new Cache(Cache.Type.Positive);
private static Cache negativeCache = new Cache(Cache.Type.Negative);
So I doubt you'll find anything doing this out of the box and guess that you'll have to play with reflection to achieve your goal.
Above answer does not work in Java 8 anymore.
Here a slight adaption:
import java.lang.reflect.Field;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
public class DNSCache {
public static void main(String[] args) throws Exception {
InetAddress.getByName("stackoverflow.com");
InetAddress.getByName("www.google.com");
InetAddress.getByName("www.yahoo.com");
InetAddress.getByName("www.example.com");
try {
InetAddress.getByName("nowhere.example.com");
} catch (UnknownHostException e) {
}
String addressCache = "addressCache";
System.out.println(addressCache);
printDNSCache(addressCache);
String negativeCache = "negativeCache";
System.out.println(negativeCache);
printDNSCache(negativeCache);
}
private static void printDNSCache(String cacheName) throws Exception {
Class<InetAddress> klass = InetAddress.class;
Field acf = klass.getDeclaredField(cacheName);
acf.setAccessible(true);
Object addressCache = acf.get(null);
Class cacheKlass = addressCache.getClass();
Field cf = cacheKlass.getDeclaredField("cache");
cf.setAccessible(true);
Map<String, Object> cache = (Map<String, Object>) cf.get(addressCache);
for (Map.Entry<String, Object> hi : cache.entrySet()) {
Object cacheEntry = hi.getValue();
Class cacheEntryKlass = cacheEntry.getClass();
Field expf = cacheEntryKlass.getDeclaredField("expiration");
expf.setAccessible(true);
long expires = (Long) expf.get(cacheEntry);
Field af = cacheEntryKlass.getDeclaredField("addresses");
af.setAccessible(true);
InetAddress[] addresses = (InetAddress[]) af.get(cacheEntry);
List<String> ads = new ArrayList<String>(addresses.length);
for (InetAddress address : addresses) {
ads.add(address.getHostAddress());
}
System.out.println(hi.getKey() + " expires in "
+ Instant.now().until(Instant.ofEpochMilli(expires), ChronoUnit.SECONDS) + " seconds " + ads);
}
}
}
The above answer does not work with Java 11. In Java 11, both positive and negative cache entries can be retrieved using the 'cache' instance variable.
Here are new adaptations:
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.lang.reflect.Field;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
public class DnsCacheFetcher {
static long startTimeinNano = System.nanoTime();
public static void main(String[] args) throws Exception {
System.out.println("SecurityManager: " + System.getSecurityManager());
InetAddress.getByName("stackoverflow.com");
InetAddress.getByName("www.google.com");
InetAddress.getByName("www.yahoo.com");
InetAddress.getByName("www.ankit.com");
try {
InetAddress.getByName("nowhere.example.com");
} catch (UnknownHostException e) {
System.out.println("Unknown host: " + e);
}
String addressCache = "cache";
System.out.println(">>>>" + addressCache);
printDNSCache(addressCache);
/*
* String negativeCache = "negativeCache"; System.out.println(">>>>" +
* negativeCache); printDNSCache(negativeCache);
*/
}
private static void printDNSCache(String cacheName) throws Exception {
Class<InetAddress> klass = InetAddress.class;
Field[] fields = klass.getDeclaredFields();
/*
* for (Field field : fields) { System.out.println(field.getName()); }
*/
Field acf = klass.getDeclaredField(cacheName);
acf.setAccessible(true);
Object addressCache = acf.get(null);
Class cacheKlass = addressCache.getClass();
Map<String, Object> cache = (Map<String, Object>) acf.get(addressCache);
for (Map.Entry<String, Object> hi : cache.entrySet()) {
/* System.out.println("Fetching cache for: " + hi.getKey()); */
Object cacheEntry = hi.getValue();
Class cacheEntryKlass = cacheEntry.getClass();
Field expf = cacheEntryKlass.getDeclaredField("expiryTime");
expf.setAccessible(true);
long expires = (Long) expf.get(cacheEntry);
Field af = cacheEntryKlass.getDeclaredField("inetAddresses");
af.setAccessible(true);
InetAddress[] addresses = (InetAddress[]) af.get(cacheEntry);
List<String> ads = null;
if (addresses != null) {
ads = new ArrayList<String>(addresses.length);
for (InetAddress address : addresses) {
ads.add(address.getHostAddress());
}
}
/*
* System.out.println(hi.getKey() + " expires in " +
* (Instant.now().until(Instant.ofEpochMilli(expires), ChronoUnit.SECONDS)) +
* " seconds. inetAddresses: " + ads);
*/
/*
* System.nanoTime() + 1000_000_000L * cachePolicy : this how java 11 set
* expiryTime
*/
System.out.println(hi.getKey() + " expires in approx " + (expires - startTimeinNano) / 1000_000_000L
+ " seconds. inetAddresses: " + ads);
}
}}
https://github.com/alibaba/java-dns-cache-manipulator
A simple 0-dependency thread-safe Java™ lib for setting/viewing dns programmatically without touching host file, make unit/integration test portable; and a tool for setting/viewing dns of running JVM process.
This lib/tool read and set java dns cache by reflection, with concerns:
compatibility with different java version(support Java 6/8/11/17).
dns cache implementation in java.net.InetAddress is different in different java version.
thread-safety
support IPv6

Categories

Resources