How to build a port scanner with Javascript using React Native

Javascript port scanner in react native

Hello!

Why bother writing a port scanner in Javascript you might ask? Well javascript is many things, however the majority of its use is based on its original design to be synchronously executed in-browser using a single thread. Because of this, it becomes a challenge to write code outside of this original use case, though not impossible. The challenge of interacting with a local network using a language like Javascript then becomes an interesting endeavour (at least to me).

If you take this type of a challenge and integrate it into a javascript based framework like react native, the potential increases exponentially. If one can successfully implement this type of network analysis on react native, then the use-case expands to platforms like iOS and Android. Same goes for AngularJS or Apache Cordova.

There are many functions, libraries and modules that will help write asynchronous code. One of which is async. Many helper functions are included in this module to assist writing dependent, asynchronous and linear processing code.

The problem, when writing something like a port scanner, is there are main dependencies that need to be established before you start blasting IP addresses with TCP requests and wait for responses. We need to know things like are we on a local network? Do we have a local IP? Whats the subnet? What ports are we going to scan?

Since some of these requests take a bit of processing and calculation, we need to either execute the code asynchronously using a module like async or we can use promises to ensure that certain blocks of code are executed before subsequent blocks of code are executed.

The goal of this project is to ultimately complete the following tasks :

  1. Import the dependencies you will need to interact with your network
  2. Gather information about your local network
  3. Scan the list of IPs in your subnet with defined ports
  4. Report on the IPs that responded or have open connections on the defined ports

Import the dependencies you will need to interact with your network

First and foremost before we can do anything, we need to include the dependencies that we will need for port scanning in react native.

import { NetworkInfo } from 'react-native-network-info';
import SubnetmaskModule from 'get-subnet-mask';
var sip = require ('shift8-ip-func');
var ipaddr = require('ipaddr.js');
var net = require('react-native-tcp');

Since we’re going the route of react native, we’ll need to include a few dependencies in our project to start. The first dependency is the react-native-network-info depdendency. This is a react-native specific dependency because it allows you to gather the local device’s network information so that we can extrapolate and utilize

The next dependency is get-subnet-mask. Similar idea to the network info module above, however where that module falls short this module makes up for it. As implied by the name we need the local subnet mask in order to ultimately calculate an IP range to scan of your local network.

Next in the list we have our own npm package shift8-ip-func. We wrote and published this module because it could be re-used elsewhere and nothing exactly existed as a package. All this does is calculate the range of IP addresses between two IP addresses that are converted to hex codes. This module is key in determining the list of IPs to scan in the first place.

The next package is the wonderfully useful and integral ipaddr.js library. This is an IPv6 and IPv4 address manipulation library. It does everything with IP calculation except calculate range of IPs (which is why we wrote our own that did that).

The last package in this list is definitely the most important. It is a react-native version of the NodeJS Net api, affably called react-native-tcp. This allows us to interact with and establish TCP network connections with IP addresses. This is key for us to be able to build a functional port scanner.

Gather information about your local network

Now that we have the dependencies we need, lets write a Javascript promise to gather the network information in a specific order to control the flow of information so that we can calculate the IP range and whatever else we may need in our port scanner.

    var network_promise = new Promise(function(resolve, reject) {
        NetworkInfo.getIPAddress(ip => { 
          local_ip = ip; 
          NetworkInfo.getBroadcast(address => { 
            local_broadcast = address; 
              SubnetmaskModule.getSubnet((sb) => {
                local_netmask = sb;
                subconv = ipaddr.IPv4.parse(local_netmask).prefixLengthFromSubnetMask();
                firstHost = ipaddr.IPv4.networkAddressFromCIDR(local_ip + "/" + subconv);
                lastHost = ipaddr.IPv4.broadcastAddressFromCIDR(local_ip + "/" + subconv);
                firstHostHex = sip.convertIPtoHex(firstHost);
                lastHostHex = sip.convertIPtoHex(lastHost);
                ipRange = sip.getIPRange(firstHostHex,lastHostHex);
                ipRange = ipRange.slice(1); // Remove the first ip in the array
                // Resolve all the calculated values
                resolve({
                  local_ip: local_ip, 
                  local_broadcast: local_broadcast, 
                  local_netmask: local_netmask, 
                  subnet_conv: subconv, 
                  first_host: firstHost, 
                  last_host: lastHost,
                  first_host_hex: firstHostHex, 
                  last_host_hex: lastHostHex, 
                  ip_range: ipRange
                });
              });
          });
        });
    });

So whats happening above? We are creating a new Promise with a chain of functions that will execute in a specific order. First we want to get the local IP address of the person running the code. Then we want to extrapolate the broadcast address and netmask. Then we are using the ipaddr.js and shift8-ip-func packages to get the subnet mask, first host in the subnet and last host in the subnet.

Then we need to convert those first and last hosts to hex codes so that they can be converted into an IP range. We then want to remove the first host in that range because it usually is the 192.168.0.0 (network address).

The last variable that we care about is the ip_range variable. Even though we are only going to be using that variable, we are sending all the calculated variables in the resolve.

Scan the list of IPs in your subnet with defined ports

Now that we have all the information we need, we’ll need to build a function to scan each IP. Ultimately we will be building nested for-loops to cycle through the IPs in the list as well as the TCP ports that we want to scan, which we will define as an array :

var portRange = [ 22, 80, 443 ];

Since we will be running this function many times, likely more than once against each IP, we will want to define a robust Promise based function that will resolve once complete before being executed again. This will ensure that we dont quickly overwhelm resources or bottleneck network traffic. It also allows us to build in throttling later on if that does become a problem.

// Function to scan hosts
var scanHost = function(hostIP, hostPort) {
  return new Promise(function (resolve,reject) {
    var client = net.connect({
      host: hostIP,
      port: hostPort
    }, 
    function() { //'connect' listener
      console.log('Connected');
    });
    
    client.setTimeout(2000,function(){
        // called after timeout -> same as socket.on('timeout')
        // it just tells that soket timed out => its ur job to end or destroy the socket.
        // socket.end() vs socket.destroy() => end allows us to send final data and allows some i/o activity to finish before destroying the socket
        // whereas destroy kills the socket immediately irrespective of whether any i/o operation is goin on or not...force destry takes place
        console.log('Socket timed out');
    });

    client.on('connect', function() {
        var scan_result = {
          ip:hostIP, 
          port:hostPort
        };
        resolve(scan_result);
    })

    client.on('timeout',function(){
      console.log('Socket timed out !');
      client.end('Timed out!');
      // can call socket.destroy() here too.
    });

    client.on('end',function(data){
      console.log('Socket ended from other end!');
      console.log('End data : ' + data);
    });

    client.on('close',function(error){
      var bread = client.bytesRead;
      var bwrite = client.bytesWritten;
      console.log('Bytes read : ' + bread);
      console.log('Bytes written : ' + bwrite);
      console.log('Socket closed!');
      if(error){
        console.log('Socket was closed as a result of transmission error');
      }
    }); 

    client.on('error', function(err) {
      console.log('******* ERROR : ' + JSON.stringify(err));
      client.destroy();
    });

    setTimeout(function(){
      var isdestroyed = client.destroyed;
      console.log('Socket destroyed:' + isdestroyed);
      client.destroy();
    },5000);
  });
}

The scanHost function has a bit going on there. Basically we are opening a socket connection to the defined IP and Port. If the connection opens, we consider that port open and resolve the promise by sending an object of the IP and port so that we can ultimately notify the end user that hey that ip and port are open!

The other functions that should be considered are the “client.on” conditions, such as close, error and timeout. Its all pretty self explanatory and there is plenty of examples and additional documentation on the react-native-tcp github page.

Report on the IPs that responded or have open connections on the defined ports

This is the last step in the process, but I’ll cover the actual trigger for everything to be initiated, including gathering the network information.

    network_promise.then((response) => {
      for (let i = 0; i < response["ip_range"].length; i++) {
        for (let j = 0; j < portRange.length; j++) { scanHost(response["ip_range"][i], portRange[j]) .then(response => {
            scanResult.push(response);
            this.setState({ 
              listContent: this.state.listContent.concat([response])
            });
          })
          .catch(err => {
            console.error(err);
            return err;
          })
        }
      }
    })
    .catch(err => {
      console.error(err);
      return err;
    })

As you can see, we start with the network_promise, then the nested for-loops that trigger the scanHost function which will then allow us to extrapolate the resolve of the scanHost function and use react-native’s setState to update the view for the end-user to see the results.

If you weren’t using react native, instead of setState, you could simply push the results to an array which you can see that we are actually doing anyways with scanResult.push(response).

Thats it! In a nutshell, we covered how to gather network information, extrapolate the information we need to build a range of local IP addresses on your network, scan them with a defined array of TCP ports and then report back on the results.

Port scanning and banner grabbing with Javascript

One of the interesting prospects that could take this entire endeavour a step further would be to build a system for banner grabbing. If you have used command-line port scanners like nmap, then you already know that it is very useful in network analysis to have the ability to extrapolate not only the services that are running on certain ports, but the versions and other important information that is returned when interacting with the services by mimicking a legitimate request.

Find an example below that scans an IP address and a port and then simulates an HTTP request (assuming it is port 80 or 443) in order to get the banner response. This could be used to extrapolate the Apache/Nginx/Lighttpd version as well as any other important information that is disclosed.

function scanTCPHost(host, port) {
  var client = net.createConnection(port, host);
  console.log('Socket created.');
  client.on('data', function(data) {
    // Log the response from the HTTP server.
    console.log('RESPONSE: ' + data);
  }).on('connect', function() {
    // Manually write an HTTP request.
    client.write("GET / HTTP/1.0\r\n\r\n");
    console.log('CONNECTED : ' + host + ' ' + port);
  }).on('end', function() {
    console.log('DONE');
    client.close();
  });
}

Interesting, isnt it? Who said Javascript should be restricted to the browser? These exercises are interesting in that they push a language or framework to the absolute limit of what it was designed to do, sometimes utilizing modules and packages that are designed for something else (Browser sockets) and using them for something more (Port scanning).

I hope you found this useful!

Subscribe
Notify of
guest

15 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
HappyKumamon
HappyKumamon
3 years ago

Good job! Innovated idea!

shift8web
shift8web
3 years ago
Reply to  HappyKumamon

Thank you!

behnam
behnam
3 years ago

Hello

if it is possible please send me the complete project file. I’m new in React native and do not know how these are works. thanks

shift8web
shift8web
3 years ago
Reply to  behnam

Hello – you can see our github project here : https://github.com/stardoth

behnam
behnam
3 years ago
Reply to  shift8web

Thank you

I’m wondering, does it works on android emulator?

shift8web
shift8web
3 years ago
Reply to  behnam

On the emulator it does , yes. It uses web sockets which should work on any device thats connected to the network. If you are able to test on android and provide insight (perhaps submit as issues on github) , that would be helpful

behnam
behnam
3 years ago
Reply to  shift8web

I commanded npm install react-native and then react-native run-android but it doesnt works for me
what I should to do?
this is the error

error React Native CLI uses autolinking for native dependencies, but the following modules are linked manually:
– get-subnet-mask (to unlink run: “react-native unlink get-subnet-mask”)
– react-native-network-info (to unlink run: “react-native unlink react-native-network-info”)
– react-native-sockets (to unlink run: “react-native unlink react-native-sockets”)
– react-native-tcp (to unlink run: “react-native unlink react-native-tcp”)
This is likely happening when upgrading React Native from below 0.60 to 0.60 or above. Going forward, you can unlink this dependency via “react-native unlink ” and it will be included in your app automatically. If a library isn’t compatible with autolinking, disregard this message and notify the library maintainers.
Read more about autolinking: https://github.com/react-na

shift8web
shift8web
3 years ago
Reply to  behnam

I updated the readme for the project with installation instructions. If you have further issues, please open an issue on the github project and we can discuss there.

behnam
behnam
3 years ago
Reply to  shift8web

How I can specified the host ip and port number in it?

behnam
behnam
3 years ago
Reply to  shift8web

I changed it but still it does not work. I tried with LAN and WIFI but it does not work with both. check the below picture
https://www.dropbox.com/s/w

Jaron Smith
Jaron Smith
3 years ago

I have gone through several of your articles. In terms of providing content, you are doing an amazing job. And I must say, this particular post was one of the most informative ones. Keep on helping developers community with such posts.

Pål Nilsen
Pål Nilsen
3 years ago

This is very useful, but I keep hitting some limit in React Native where it gives me an error like “rejected from java.util.concurrent.threadpoolexecutor”. If I add some strategic sleep’s it helps, but scanning 252 hosts takes 20 seconds.
Any ideas how to be able to scan everything at once without crashing?

Eduardo Peloso Maia
Eduardo Peloso Maia
2 years ago

hey, i got a lot of errors here, if someone could help me , call me on facebok and I show my errors…Thanks

Gajjar Tejas
Gajjar Tejas
1 year ago

I made a library based on this tutorial, https://github.com/gajjarte

Hope this will helps someone.

shift8web
shift8web
1 year ago
Reply to  Gajjar Tejas

This is fantastic! I have starred your repository 🙂