summaryrefslogtreecommitdiff
path: root/src/main/java/com/it_jaros/jscanner/scan/engine/ScanHostTask.java
blob: 7a01d54c58fe1da237d3d519b47caaae8c7a489c (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
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<ScanResult> {

    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<PortResult> 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<PortResult> poll = getPortResult(state);
                if (poll instanceof PollState.Success<PortResult>(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<PortResult> getPortResult(ProducerState<PortResult> state) throws InterruptedException {
        Future<PortResult> 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();
        }
    }
}