1

By reading this article I was able to partially write a DNS server query based resolver that I added to the HostResolutionRequestInterceptor component.

But I'm unable to get all hostnames via IP using java.net.DatagramSocket and I can't implement the getAllHostNamesForHostAddress of the HostResolver interface. This is the component I wrote:

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;

import org.burningwave.tools.net.HostResolver;
import org.burningwave.tools.net.IPAddressUtil;

public class IPV4DNSServerHostResolver implements HostResolver {
    private static Random requestIdGenerator;

    static {
        requestIdGenerator = new Random();
    }

    private InetAddress dNSServerIP;
    private int dNSServerPort;

    public IPV4DNSServerHostResolver(String dNSServerIP, int dNSServerPort) throws UnknownHostException {
        this.dNSServerIP = InetAddress.getByName(dNSServerIP);
        this.dNSServerPort = dNSServerPort;
    }

    @Override
    public Collection<InetAddress> getAllAddressesForHostName(Map<String, Object> argumentsMap) {
        return resolveHostForName((String)getMethodArguments(argumentsMap)[0]);
    }

    public Collection<InetAddress> resolveHostForName(String hostName) {
        try {
            Collection<InetAddress> addresses = new ArrayList<>();
            byte[] response = sendRequest(hostName);
            Map<String, String> iPToDomainMap = parseResponse(response);
            for (Map.Entry<String, String> iPToDomain : iPToDomainMap.entrySet()) {
                addresses.add(InetAddress.getByAddress(iPToDomain.getValue(), IPAddressUtil.INSTANCE.textToNumericFormat(iPToDomain.getKey())));
            }
            return addresses;
        } catch (Throwable exc) {
            return sneakyThrow(exc);
        }
    }

    private byte[] sendRequest(String hostName) throws IOException, SocketException {
        short ID = (short)requestIdGenerator.nextInt(32767);
        try (
            ByteArrayOutputStream requestContentStream = new ByteArrayOutputStream();
            DataOutputStream requestWrapper = new DataOutputStream(requestContentStream);
        ) {
            short requestFlags = Short.parseShort("0000000100000000", 2);
            ByteBuffer byteBuffer = ByteBuffer.allocate(2).putShort(requestFlags);
            byte[] flagsByteArray = byteBuffer.array();

            short QDCOUNT = 1;
            short ANCOUNT = 0;
            short NSCOUNT = 0;
            short ARCOUNT = 0;

            requestWrapper.writeShort(ID);
            requestWrapper.write(flagsByteArray);
            requestWrapper.writeShort(QDCOUNT);
            requestWrapper.writeShort(ANCOUNT);
            requestWrapper.writeShort(NSCOUNT);
            requestWrapper.writeShort(ARCOUNT);

            String[] domainParts = hostName.split("\\.");

            for (int i = 0; i < domainParts.length; i++) {
                byte[] domainBytes = domainParts[i].getBytes(StandardCharsets.UTF_8);
                requestWrapper.writeByte(domainBytes.length);
                requestWrapper.write(domainBytes);
            }
            requestWrapper.writeByte(0);
            requestWrapper.writeShort(1);
            requestWrapper.writeShort(1);
            byte[] dnsFrame = requestContentStream.toByteArray();
            DatagramPacket packet;
            byte[] response;
            try (DatagramSocket socket = new DatagramSocket()){
                DatagramPacket dnsReqPacket = new DatagramPacket(dnsFrame, dnsFrame.length, dNSServerIP, dNSServerPort);
                socket.send(dnsReqPacket);
                response = new byte[1024];
                packet = new DatagramPacket(response, response.length);
                socket.receive(packet);
            }
            return response;
        }
    }

    private Map<String, String> parseResponse(byte[] responseContent) throws IOException {
        try (InputStream responseContentStream = new ByteArrayInputStream(responseContent);
            DataInputStream responseWrapper = new DataInputStream(responseContentStream)
        ) {
            responseWrapper.skip(6);
            short ANCOUNT = responseWrapper.readShort();
            responseWrapper.skip(4);
            int recLen;
            while ((recLen = responseWrapper.readByte()) > 0) {
                byte[] record = new byte[recLen];
                for (int i = 0; i < recLen; i++) {
                    record[i] = responseWrapper.readByte();
                }
            }
            responseWrapper.skip(4);
            byte firstBytes = responseWrapper.readByte();
            int firstTwoBits = (firstBytes & 0b11000000) >>> 6;
            Map<String, String> ipToDomainMap = new HashMap<>();
            try (ByteArrayOutputStream label = new ByteArrayOutputStream();) {
                for(int i = 0; i < ANCOUNT; i++) {
                    if(firstTwoBits == 3) {
                        byte currentByte = responseWrapper.readByte();
                        boolean stop = false;
                        byte[] newArray = Arrays.copyOfRange(responseContent, currentByte, responseContent.length);
                        try (InputStream responseSectionContentStream = new ByteArrayInputStream(newArray);
                            DataInputStream responseSectionWrapper = new DataInputStream(responseSectionContentStream);
                        ) {
                            ArrayList<Integer> RDATA = new ArrayList<>();
                            ArrayList<String> DOMAINS = new ArrayList<>();
                            while(!stop) {
                                byte nextByte = responseSectionWrapper.readByte();
                                if(nextByte != 0) {
                                    byte[] currentLabel = new byte[nextByte];
                                    for(int j = 0; j < nextByte; j++) {
                                        currentLabel[j] = responseSectionWrapper.readByte();
                                    }
                                    label.write(currentLabel);
                                } else {
                                    stop = true;
                                    responseWrapper.skip(8);
                                    int RDLENGTH = responseWrapper.readShort();
                                    for(int s = 0; s < RDLENGTH; s++) {
                                        int nx = responseWrapper.readByte() & 255;
                                        RDATA.add(nx);
                                    }
                                }
                                DOMAINS.add(new String( label.toByteArray(), StandardCharsets.UTF_8));
                                label.reset();
                            }

                            StringBuilder ip = new StringBuilder();
                            StringBuilder domainSb = new StringBuilder();
                            for(Integer ipPart:RDATA) {
                                ip.append(ipPart).append(".");
                            }

                            for(String domainPart:DOMAINS) {
                                if(!domainPart.equals("")) {
                                    domainSb.append(domainPart).append(".");
                                }
                            }
                            String domainFinal = domainSb.toString();
                            String ipFinal = ip.toString();
                            ipToDomainMap.put(ipFinal.substring(0, ipFinal.length()-1), domainFinal.substring(0, domainFinal.length()-1));
                        }
                    }

                    firstBytes = responseWrapper.readByte();
                    firstTwoBits = (firstBytes & 0b11000000) >>> 6;
                }
            }
            return ipToDomainMap;
        }
    }

    @Override
    public Collection<String> getAllHostNamesForHostAddress(Map<String, Object> argumentsMap) {
        byte[] addressAsByteArray = (byte[])getMethodArguments(argumentsMap)[0];
        String iPAddress = IPAddressUtil.INSTANCE.numericToTextFormat(addressAsByteArray);
        //To be implemented
        return sneakyThrow(new UnknownHostException(iPAddress));
    }

    private <T> T sneakyThrow(Throwable exc) {
        throwException(exc);
        return null;
    }


    private <E extends Throwable> void throwException(Throwable exc) throws E {
        throw (E)exc;
    }

}

Does anyone know how to get all hostnames via IP using java.net.DatagramSocket?

  • "But I'm unable to get all hostnames via IP" There is no guarantee that all IP addresses have `PTR` records, nor that this maps to the same name as a forward query on the name. Also what you wrote is a DNS client, not a server. Except for learning, I recommend you use whatever DNS library exists in your language of choice to avoid having to handle all the difficult parts of DNS (ex: name compression). That won't change the above conclusion though about `PTR` records. You also need to test the type of records you got back before trying to parse them. – Patrick Mevzek Oct 31 '22 at 13:44
  • Thanks for the answer. Now I'm trying with dnsjava library but [I'm unable to retrieve the host name by IP](https://stackoverflow.com/questions/74264551/how-to-retrieve-the-host-name-by-ip-with-dnsjava-library): do you know what's wrong? – Edgar Becker Oct 31 '22 at 14:11
  • The answer is still the same as above: not all IP addresses have `PTR` records. – Patrick Mevzek Oct 31 '22 at 15:26

1 Answers1

1

In the end, I used the dnsjava library because creating this component with the java.net.DatagramSocket class is too complicated. This is the component I created:

import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
import java.util.function.Supplier;

import org.burningwave.tools.net.HostResolver;
import org.xbill.DNS.AAAARecord;
import org.xbill.DNS.ARecord;
import org.xbill.DNS.Name;
import org.xbill.DNS.Record;
import org.xbill.DNS.ReverseMap;
import org.xbill.DNS.SimpleResolver;
import org.xbill.DNS.TextParseException;
import org.xbill.DNS.Type;
import org.xbill.DNS.lookup.LookupResult;
import org.xbill.DNS.lookup.LookupSession;

public class DNSJavaHostResolver implements HostResolver {

    private LookupSession lookupSession;

    public DNSJavaHostResolver(String dNSServerIP) {
        try {
            lookupSession = LookupSession.builder().resolver(
                new SimpleResolver(InetAddress.getByName(dNSServerIP))
            ).build();
        } catch (UnknownHostException exc) {
            sneakyThrow(exc);
        }
    }

    @Override
    public Collection<InetAddress> getAllAddressesForHostName(Map<String, Object> argumentsMap) {
        Collection<InetAddress> hostInfos = new ArrayList<>();
        String hostName = (String)getMethodArguments(argumentsMap)[0];
        findAndProcessHostInfos(
            () -> {
                try {
                    return Name.fromString(hostName.endsWith(".") ? hostName : hostName + ".");
                } catch (TextParseException exc) {
                    return sneakyThrow(exc);
                }
            },
            record -> {
                if (record instanceof ARecord) {
                    hostInfos.add(((ARecord)record).getAddress());
                } else if (record instanceof AAAARecord) {
                    hostInfos.add(((AAAARecord)record).getAddress());
                }
            },
            Type.A, Type.AAAA
        );
        return hostInfos;
    }


    @Override
    public Collection<String> getAllHostNamesForHostAddress(Map<String, Object> argumentsMap) {
        Collection<String> hostNames = new ArrayList<>();
        findAndProcessHostInfos(
            () ->
                ReverseMap.fromAddress((byte[])getMethodArguments(argumentsMap)[0]),
            record ->
                hostNames.add(((PTRRecord)record).getTarget().toString(true)),
            Type.PTR
        );
        return hostNames;
    }

    private void findAndProcessHostInfos(
        Supplier<Name> nameSupplier,
        Consumer<Record> recordProcessor,
        int... types
    ) {
        Collection<CompletableFuture<LookupResult>> hostInfoRetrievers = new ArrayList<>();
        for (int type : types) {
            hostInfoRetrievers.add(
                lookupSession.lookupAsync(nameSupplier.get(), type).toCompletableFuture()
            );
        }
        hostInfoRetrievers.stream().forEach(hostNamesRetriever -> {
            try {
                List<Record> records = hostNamesRetriever.join().getRecords();
                if (records != null) {
                    for (Record record : records) {
                        recordProcessor.accept(record);
                    }
                }
            } catch (Throwable exc) {

            }
        });
    }

    private <T> T sneakyThrow(Throwable exc) {
        throwException(exc);
        return null;
    }


    private <E extends Throwable> void throwException(Throwable exc) throws E {
        throw (E)exc;
    }

}

And this the code to add it to the HostResolutionRequestInterceptor:

HostResolutionRequestInterceptor.INSTANCE.install(
    new DNSJavaHostResolver("208.67.222.222"),//Open DNS server
    new DNSJavaHostResolver("208.67.222.220"),//Open DNS server
    DefaultHostResolver.INSTANCE
);
InetAddress inetAddress = InetAddress.getByName("stackoverflow.com");