package net.windwards.dnsfrontend.frontend;
import net.sf.ehcache.Ehcache;
import net.sf.ehcache.Element;
import net.windwards.dnsfrontend.api.AbstractTaskKeeper;
import net.windwards.dnsfrontend.api.Backend;
import net.windwards.dnsfrontend.api.BackendCommunicationException;
import net.windwards.dnsfrontend.api.BackendRepository;
import net.windwards.dnsfrontend.api.NoSuchDomainException;
import net.windwards.dnsfrontend.dialog.ProtocolCodingException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xbill.DNS.Flags;
import org.xbill.DNS.Message;
import org.xbill.DNS.NULLRecord;
import org.xbill.DNS.Opcode;
import org.xbill.DNS.Rcode;
import org.xbill.DNS.Record;
import org.xbill.DNS.Section;
import java.io.IOException;
public class ResolveTask implements Runnable {
private Logger logger = LoggerFactory.getLogger(ResolveTask.class);
protected final Query query;
protected Ehcache cache;
protected AbstractTaskKeeper waitHere;
protected String ident;
private BackendRepository backends;
final private Object DontExist = new Object();
public ResolveTask(Query query) {
this.query = query;
}
public void setStagingZone(AbstractTaskKeeper zone) {
this.waitHere = zone;
}
public void setCache(Ehcache cache) {
this.cache = cache;
}
public void setBackendStore(BackendRepository backendStore) {
this.backends = backendStore;
}
public String getIdent() {
return this.ident;
}
@Override
public void run() {
try {
this.query.interpret();
} catch (IOException e) {
this.fail("Could not parse query");
return;
}
if (this.query.getMessage().getHeader().getOpcode() != Opcode.QUERY) {
this.refuse();
return;
}
Record question = this.query.getMessage().getQuestion();
Backend backend = this.backends.lookup(question.getType());
if (backend == null) {
this.refuse();
return;
}
this.ident = backend.makeKey(question);
logger.debug("Checking cache [key={}]", this.ident);
// check cache
Element lmnt = this.cache.get(this.getIdent());
if(lmnt != null) {
if(lmnt.getObjectValue() == DontExist) {
this.timeout();
} else {
Record record = (Record) lmnt.getObjectValue();
this.answer(record, true);
}
return;
}
logger.debug("Asking backend [query={}]", this.query);
// Park this query awaiting result from backend
this.waitHere.keep(this);
try {
// Notify backend we need an update
backend.notify(question);
} catch (NoSuchDomainException e) {
this.waitHere.discard(this);
this.unknown();
} catch (BackendCommunicationException e) {
this.waitHere.discard(this);
this.fail("JGroups failure", e);
} catch (ProtocolCodingException e) {
this.waitHere.discard(this);
this.fail("Failed to encode request", e);
}
}
private void processMessage(Message message) {
try {
this.query.reply(message);
} catch (IOException e) {
logger.warn("Failure sending reply [query={}]", this.query);
}
}
public void answer(Record record, boolean cached) {
if(record.getType() != this.query.getMessage().getQuestion().getType()) {
this.fail("Question/Answer mismatch");
return;
}
Message message = new Message();
message.getHeader().setFlag(Flags.QR);
message.getHeader().setFlag(Flags.AA);
message.addRecord(record, Section.ANSWER);
String logmsg = cached ? "Cached answer" : "Backend answer";
logger.info("{} [query={}, record={}]", logmsg, this.query, record);
this.processMessage(message);
}
public void unknown() {
Message message = new Message();
message.getHeader().setFlag(Flags.QR);
message.getHeader().setRcode(Rcode.NXDOMAIN);
logger.info("Unknown domain [query={}]", this.query);
this.processMessage(message);
}
public void fail(String cause, Throwable... exceptions) {
Message message = new Message();
message.getHeader().setFlag(Flags.QR);
message.getHeader().setRcode(Rcode.SERVFAIL);
if(exceptions.length == 0) {
logger.warn("{} [query={}]", cause, this.query);
} else {
logger.warn("{} [query={}]", cause, this.query, exceptions[0]);
}
this.processMessage(message);
}
public void timeout() {
Message message = new Message();
message.getHeader().setFlag(Flags.QR);
message.getHeader().setFlag(Flags.AA);
message.getHeader().setRcode(Rcode.NXDOMAIN);
logger.info("No answer [query={}]", this.query);
this.processMessage(message);
}
public void refuse() {
Message message = new Message();
message.getHeader().setFlag(Flags.QR);
message.getHeader().setRcode(Rcode.REFUSED);
logger.info("Not implemented type [query={}]", this.query);
this.processMessage(message);
}
}