package org.handwerkszeug.dns.zone; import static org.handwerkszeug.util.Validation.notNull; import java.util.HashSet; import java.util.NavigableSet; import java.util.Set; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentSkipListMap; import java.util.concurrent.ConcurrentSkipListSet; import org.handwerkszeug.dns.Name; import org.handwerkszeug.dns.RCode; import org.handwerkszeug.dns.RRType; import org.handwerkszeug.dns.ResourceRecord; import org.handwerkszeug.dns.Response; import org.handwerkszeug.dns.ZoneType; import org.handwerkszeug.dns.record.SOARecord; import org.handwerkszeug.dns.server.CNAMEResponse; import org.handwerkszeug.dns.server.DNAMEResponse; import org.handwerkszeug.dns.server.NoErrorResponse; import org.handwerkszeug.dns.server.NotFoundResponse; import org.handwerkszeug.dns.server.ReferralResponse; public class MasterZone extends AbstractZone { final ConcurrentMap>> records = new ConcurrentSkipListMap>>(); final Response nxDomain; final Response nxRRSet; public MasterZone(Name name, SOARecord soaRecord) { super(ZoneType.master, name); this.nxDomain = new NotFoundResponse(RCode.NXDomain, soaRecord); this.nxRRSet = new NotFoundResponse(RCode.NXRRSet, soaRecord); } @Override public Response find(Name qname, RRType qtype) { notNull(qname, "qname"); notNull(qtype, "qtype"); if (qname.contains(this.name()) == false) { return this.nxDomain; } ConcurrentMap> exactMatch = this.records .get(qname); if (exactMatch != null) { NavigableSet rrs = exactMatch.get(qtype); if (rrs != null) { synchronized (rrs) { if (rrs.isEmpty() == false) { return new NoErrorResponse(rrs); } } } if (RRType.ANY.equals(qtype)) { Set newset = new HashSet(); for (RRType type : exactMatch.keySet()) { Set s = exactMatch.get(type); if (s != null) { synchronized (s) { newset.addAll(s); } } } if (newset.isEmpty() == false) { return new NoErrorResponse(newset); } } if (RRType.CNAME.equals(qtype) == false) { rrs = exactMatch.get(RRType.CNAME); if (rrs != null) { synchronized (rrs) { if (rrs.isEmpty() == false) { return new CNAMEResponse(rrs.first(), qtype); } } } } return this.nxRRSet; } for (Name qn = qname.toParent(); this.name().equals(qn) == false; qn = qn .toParent()) { ConcurrentMap> match = this.records .get(qn); if (match != null) { synchronized (match) { if (match.isEmpty() == false) { NavigableSet set = match.get(RRType.NS); if ((set != null) && (set.isEmpty() == false)) { return new ReferralResponse(set); } set = match.get(RRType.DNAME); if ((set != null) && (set.isEmpty() == false)) { return new DNAMEResponse(set.first(), qname, qtype); } } } } } for (Name qn = qname; this.name().equals(qn) == false; qn = qn .toParent()) { Name wild = qn.toWildcard(); ConcurrentMap> match = this.records .get(wild); if (match != null) { synchronized (match) { if (match.isEmpty() == false) { Set matchSet = match.get(qtype); if (matchSet.isEmpty() == false) { Set set = new HashSet( matchSet.size()); for (ResourceRecord rr : matchSet) { set.add(rr.toQnameRecord(qname)); } return new NoErrorResponse(set); } } } } } return this.nxDomain; } // add and remove needs queuing? // if modify operations works on single thread, not conflict. public void add(ResourceRecord rr) { notNull(rr, "rr"); for (;;) { ConcurrentMap> current = this.records .get(rr.name()); if (current == null) { ConcurrentMap> newone = new ConcurrentSkipListMap>(); NavigableSet newset = new ConcurrentSkipListSet(); newset.add(rr); newone.put(rr.type(), newset); ConcurrentMap> prevTypes = this.records .putIfAbsent(rr.name(), newone); if (prevTypes == null) { break; } synchronized (prevTypes) { Set prevRecs = prevTypes.putIfAbsent( rr.type(), newset); if (prevRecs == null) { break; } prevRecs.add(rr); break; } } else { synchronized (current) { Set rrs = current.get(rr.type()); if (rrs == null) { NavigableSet newset = new ConcurrentSkipListSet(); newset.add(rr); current.put(rr.type(), newset); break; } if (rrs.isEmpty() == false) { rrs.add(rr); break; } } } } } public void remove(ResourceRecord rr, boolean checkSets, boolean checkMap) { notNull(rr, "rr"); ConcurrentMap> current = this.records .get(rr.name()); if (current != null) { synchronized (current) { NavigableSet sets = current.get(rr.type()); sets.remove(rr); if (checkSets && sets.isEmpty()) { current.remove(rr.type()); if (checkMap && current.isEmpty()) { this.records.remove(rr.name()); } } } } } }