All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.datakernel.dns.DnsClientSocketHandler Maven / Gradle / Ivy

/*
 * Copyright (C) 2015 SoftIndex LLC.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package io.datakernel.dns;

import io.datakernel.async.Callback;
import io.datakernel.async.SettableStage;
import io.datakernel.async.Stage;
import io.datakernel.bytebuf.ByteBuf;
import io.datakernel.dns.DnsMessage.ResponseErrorCode;
import io.datakernel.eventloop.AsyncUdpSocket;
import io.datakernel.eventloop.Eventloop;
import io.datakernel.eventloop.ScheduledRunnable;
import io.datakernel.eventloop.UdpPacket;
import io.datakernel.exception.AsyncTimeoutException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.InetSocketAddress;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

final class DnsClientSocketHandler implements AsyncUdpSocket.EventHandler {
	@SuppressWarnings("ThrowableInstanceNeverThrown")
	private static final AsyncTimeoutException TIMEOUT_EXCEPTION = new AsyncTimeoutException();
	private final Logger logger = LoggerFactory.getLogger(this.getClass());

	private final Map>> resultHandlers = new HashMap<>();
	private final Eventloop eventloop;
	private final AsyncUdpSocket socket;

	private AsyncDnsClient.Inspector inspector;

	private DnsClientSocketHandler(Eventloop eventloop, AsyncUdpSocket socket, AsyncDnsClient.Inspector inspector) {
		this.eventloop = eventloop;
		this.socket = socket;
		this.socket.setEventHandler(this);
		this.inspector = inspector;
	}

	public static DnsClientSocketHandler create(Eventloop eventloop, AsyncUdpSocket udpSocket,
	                                            AsyncDnsClient.Inspector inspector) {
		return new DnsClientSocketHandler(eventloop, udpSocket, inspector);
	}

	public void resolve4(String domainName, InetSocketAddress dnsServerAddress, long timeout, Callback callback) {
		resolve(domainName, dnsServerAddress, timeout, callback, false);
	}

	Stage resolve4(String domainName, InetSocketAddress dnsServerAddress, long timeout) {
		SettableStage result = SettableStage.create();
		resolve(domainName, dnsServerAddress, timeout, result, false);
		return result;
	}

	public void resolve6(String domainName, InetSocketAddress dnsServerAddress, long timeout, Callback callback) {
		resolve(domainName, dnsServerAddress, timeout, callback, true);
	}

	Stage resolve6(String domainName, InetSocketAddress dnsServerAddress, long timeout) {
		SettableStage result = SettableStage.create();
		resolve(domainName, dnsServerAddress, timeout, result, true);
		return result;
	}

	private void resolve(String domainName, InetSocketAddress dnsServerAddress, long timeout,
	                     Callback callback, boolean ipv6) {

		Callback callbackWithTimeout = new Callback() {

			private final ScheduledRunnable timeouter = eventloop.delay(timeout, () -> {
				Set> callbacks = resultHandlers.get(domainName);
				callbacks.remove(this);
				if (callbacks.isEmpty()) resultHandlers.remove(domainName);
				setException(TIMEOUT_EXCEPTION);
			});

			@Override
			public void set(DnsQueryResult result) {
				if (!timeouter.isCancelled() && !timeouter.isComplete()) {
					timeouter.cancel();
					if (inspector != null) inspector.onDnsQueryResult(domainName, result);
					callback.set(result);
				}
			}

			@Override
			public void setException(Throwable e) {
				if (!timeouter.isCancelled() && !timeouter.isComplete()) {
					if (e instanceof AsyncTimeoutException) {
						e = new DnsException(domainName, ResponseErrorCode.TIMED_OUT);
					}
					timeouter.cancel();
					if (inspector != null) inspector.onDnsQueryError(domainName, e);
					callback.setException(e);
				}
			}
		};

		Set> callbacks = resultHandlers.computeIfAbsent(domainName, $ -> new HashSet<>());
		callbacks.add(callbackWithTimeout);
		ByteBuf query = DnsMessage.newQuery(domainName, ipv6);
		if (inspector != null) inspector.onDnsQuery(domainName, query);
		UdpPacket queryPacket = UdpPacket.of(query, dnsServerAddress);
		socket.send(queryPacket);

		socket.receive();
	}

	public void close() {
		socket.close();
	}

	public int getNumberOfRequestsInProgress() {
		return resultHandlers.size();
	}

	public String[] getDomainNamesBeingResolved() {
		Set domainNamesBeingResolved = resultHandlers.keySet();
		return domainNamesBeingResolved.toArray(new String[domainNamesBeingResolved.size()]);
	}

	public boolean allRequestsCompleted() {
		return resultHandlers.size() == 0;
	}

	// region AsyncUdpSocket.EventHandler implementation
	@Override
	public void onRegistered() {
	}

	@Override
	public void onReceive(UdpPacket packet) {
		byte[] response = packet.getBuf().array();

		try {
			DnsQueryResult dnsQueryResult = DnsMessage.getQueryResult(response);

			String domainName = dnsQueryResult.getDomainName();

			Set> callbacks = resultHandlers.remove(domainName);

			if (callbacks != null) {
				if (dnsQueryResult.isSuccessful()) {
					for (Callback callback : callbacks) {
						callback.set(dnsQueryResult);
					}
				} else {
					DnsException exception = dnsQueryResult.getException();
					for (Callback callback : callbacks) {
						callback.setException(exception);
					}
				}
			}
		} catch (DnsResponseParseException e) {
			logger.info("Received packet cannot be parsed as DNS server response.", e);
		} finally {
			packet.recycle();
		}
	}

	@Override
	public void onSend() {
	}

	@Override
	public void onClosedWithError(Exception e) {
	}
	// endregion
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy