Building a port scanner

Today Jan W. released SWI-Prolog 8.3.3 which included a modified version of Jan B. balance/2,3 named concurrent_and/2,3.

So I gave it a try.

The first thing I learned the hard way is that you have to accept each answer, e.g. press the space bar after true is displayed.

concurrent_scan(IP_address,Low_port,High_port,Number_of_threads) :-
    concurrent_and(
        between(Low_port,High_port,Port),
        port_scan(IP_address,Port),
        [threads(Number_of_threads)]
    ).

port_scan(IP_address,Port) :-
    catch(
        setup_call_cleanup(
            tcp_socket(Socket),
            (
                % Open stream socket based on TCP/IP which uses IP address and port number, i.e. INET socket
                tcp_connect(Socket, IP_address:Port),
                format('Port ~w: open~n', [Port])
            ),
            tcp_close_socket(Socket)
        ),
        error(_,_),
        true
    ).

Example run

?- concurrent_scan('140.211.166.101',79,82,3).
Port 80: open
true ;
true ;
true ;
true ;
false.

Adding fail to the end of the query will fix having to press the space bar but this is not the way concurrent_and/2 is designed to work, e.g.

?- concurrent_scan('140.211.166.101',79,82,3),fail.
Port 80: open
false.

If one looks at the test cases in test_balance.pl one sees the use of setof/3. Incorporating setof/3 leads to

concurrent_scan_02(IP_address,Low_port,High_port,Number_of_threads,Filtered_results) :-
    setof(
        Port-Result,
        concurrent_and(
            between(Low_port,High_port,Port),
            port_scan_02(IP_address,Port,Result),
            [threads(Number_of_threads)]
        ),
        Results
    ),
    include(filter,Results,Filtered_results).

port_scan_02(IP_address,Port,Result) :-
    catch(
        setup_call_cleanup(
            tcp_socket(Socket),
            (
                % Open stream socket based on TCP/IP which uses IP address and port number, i.e. INET socket
                tcp_connect(Socket, IP_address:Port),
                Result = open
            ),
            tcp_close_socket(Socket)
        ),
        error(_,_),
        Result = closed
    ).

filter(Port-open).

Example run

?- concurrent_scan_02('140.211.166.101',79,82,3,Results).
Results = [80-open].

A more comprehensive example.

?- time(concurrent_scan_02('140.211.166.101',1,65536,8192,Results)).
% 135,209,226 inferences, 16.281 CPU in 187.520 seconds (9% CPU, 8304597 Lips)
Results = [22-open, 80-open, 443-open].

8192 threads checking 65536 ports in ~3 minutes.