package com.it_jaros.jscanner.scan.engine; import com.it_jaros.jscanner.scan.Scan; import com.it_jaros.jscanner.scan.domain.PortRange; import com.it_jaros.jscanner.scan.domain.PortResult; import com.it_jaros.jscanner.scan.domain.ScanResult; import java.io.IOException; import java.net.InetAddress; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.LockSupport; public class ScanHostTask implements Callable { private final Scan scan; private final String host; // input parameter private final ScanExecutionContext context; private final CancelledToken cancelledToken; private final boolean disableOnlineCheck; private final int maxWorkersPerHost; private final int timeoutInMillis; private final long delayInNanos; public ScanHostTask(Scan scan, String host, ScanExecutionContext context) { this.scan = scan; this.host = host; this.context = context; this.cancelledToken = context.cancelledToken(); this.delayInNanos = TimeUnit.MILLISECONDS.toNanos(Math.max(0, context.scanOptions().delayInMillis())); this.disableOnlineCheck = context.scanOptions().disableOnlineCheck(); this.maxWorkersPerHost = context.scanOptions().maxWorkersPerHost(); this.timeoutInMillis = context.scanOptions().timeoutInMillis(); } @Override public ScanResult call() { try { scan.hostStart(); if (context.cancelledToken().isCancelled()) { return ScanResult.empty(host); } return scanHostPorts(); } finally { scan.hostFinish(); } } private ScanResult scanHostPorts() { if (!disableOnlineCheck) { boolean isHostOnline = checkHostOnline(); if (!isHostOnline) { // Unreachable host return ScanResult.empty(host); } // online check also sends packets to the target system. // in order not to violate set delay time // we wait here too LockSupport.parkNanos(delayInNanos); } final PortRange portRange = new PortRange(scan.getPorts()); // producer thread scan.producerStart(); ProducerState state = new ProducerThread(context).startProducer( portRange.iterator(), maxWorkersPerHost, port -> new ScanPortTask(host, port, context) ); // consumer is the main thread // we run as long as the producer is running OR // as long as things are in pipeline waiting to be processed // ONLY exception is when cancelled is set final PortResultAccumulator accumulator = new PortResultAccumulator(host); while (!cancelledToken.isCancelled() && (state.running().get() || state.inPipeline().get() > 0)) { try { PollState poll = getPortResult(state); if (poll instanceof PollState.Success(PortResult value)) { accumulator.add(value); } else if (poll instanceof PollState.Failure(Throwable error)) { System.err.printf("scanHostPorts(%s): ScanPortTask() failed for with error %s -> %s%n", host, error.getClass().getSimpleName(), error.getMessage()); } } catch (InterruptedException ignored) { Thread.currentThread().interrupt(); } } scan.producerStop(); return accumulator.build(); } private boolean checkHostOnline() { try { return InetAddress.getByName(host).isReachable(timeoutInMillis); } catch (IOException e) { // we ignore this error because it means that the host is probably not online } return false; } private PollState getPortResult(ProducerState state) throws InterruptedException { Future portResultFuture = state.completionService().poll(ProducerThread.pollInterval.toMillis(), TimeUnit.MILLISECONDS); if (portResultFuture == null) { return new PollState.Unavailable<>(); } PortResult portResult; try { portResult = portResultFuture.get(); return new PollState.Success<>(portResult); } catch (ExecutionException e) { Throwable cause = e.getCause(); return new PollState.Failure<>(cause); } finally { state.activeWorkers().release(); state.inPipeline().decrementAndGet(); } } }