├── .babelrc
├── .gitignore
├── .npmignore
├── LICENSE
├── README.md
├── android
├── build.gradle
├── react-native-lanscan.iml
└── src
│ └── main
│ ├── AndroidManifest.xml
│ └── java
│ └── com
│ └── odinvt
│ └── lanscan
│ ├── LANScanModule.java
│ ├── LANScanReactModule.java
│ ├── impl
│ └── ManagedThreadPoolExecutor.java
│ └── utils
│ └── IPv4.java
├── main.js
└── package.json
/.babelrc:
--------------------------------------------------------------------------------
1 | { "presets": ["airbnb", "react-native"] }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | logs
3 | *.log
4 | npm-debug.log*
5 |
6 | # Runtime data
7 | pids
8 | *.pid
9 | *.seed
10 |
11 | # Directory for instrumented libs generated by jscoverage/JSCover
12 | lib-cov
13 |
14 | # Coverage directory used by tools like istanbul
15 | coverage
16 |
17 | # nyc test coverage
18 | .nyc_output
19 |
20 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
21 | .grunt
22 |
23 | # node-waf configuration
24 | .lock-wscript
25 |
26 | # Compiled binary addons (http://nodejs.org/api/addons.html)
27 | build/Release
28 | android/build/
29 |
30 | # Dependency directories
31 | node_modules
32 | jspm_packages
33 |
34 | # Optional npm cache directory
35 | .npm
36 |
37 | # Optional REPL history
38 | .node_repl_history
39 | main-tf.js
40 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | .git/
3 |
4 | build/
5 | android/build/
6 | *.pbxuser
7 | !default.pbxuser
8 | *.mode1v3
9 | !default.mode1v3
10 | *.mode2v3
11 | !default.mode2v3
12 | *.perspectivev3
13 | !default.perspectivev3
14 | *.xcworkspace
15 | !default.xcworkspace
16 | xcuserdata
17 | profile
18 | *.moved-aside
19 | DerivedData
20 |
21 | local.properties
22 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Oussama El Bacha
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # react-native-lanscan
2 | > A React Native library for scanning LAN IPs and ports to find services via device Wi-Fi
3 |
4 | ###### Support
5 | This package is only supported on **android** devices, since I'm not much of an iOS Developer. Feel free to submit a PR of an iOS port I'll be happy to merge ! :)
6 |
7 | ###### Permissions
8 | On **android**, this package adds another request for the `android.permission.ACCESS_WIFI_STATE` permission for your app in the package manifest. So no need to add the permission to your app module AndroidManifest.
9 |
10 | ###### General Information
11 | This package scans the LAN network of the device via Wi-Fi and searches for hosts with services on a port range of your choosing.
12 |
13 | Please take your precautions and check if the device is connected to the network with Wi-Fi first as this package doesn't do that for you. Use the React Native official [NetInfo](https://facebook.github.io/react-native/docs/netinfo.html) API.
14 |
15 | Please keep in mind that this is a fallback method. There are more suitable ways of finding services on the local network like mDNS of the Zero-configuration standard (implementations like Avahi, Bonjour or NSD). Take a look at the awesome [react-native-zeroconf](https://github.com/Apercu/react-native-zeroconf) package by [Balthazar Gronon](https://github.com/Apercu/). But there are switches and routers that tend to block mDNS Registry and Discovery. And this is where **react-native-lanscan** comes in handy.
16 |
17 | Here is how it works :
18 | - Fetches the device dhcp wifi Info.
19 | - Calculates the list of the possible IP Addresses on the network using the fetched device IP address and netmask.
20 | - Calculates the subnet broadcast IP address (e.g For the CIDR `192.168.1.100/24` that would be `192.168.1.255`).
21 | - Keeps sending datagram UDP packets to the subnet broadcast IP address and the port range of your choosing, and keeps waiting for hosts on the subnet to respond for a specified timeout.
22 | - If it does not find any hosts, either there aren't any or the switch is blocking subnet broadcast packets. So if you choose to do so, the package falls back to a **DeepScan**.
23 | - The **DeepScan** consists of sending ICMP ECHO requests to the list of the possible network IPs that was calculated before. (This only works if the switch is not blocking ICMP requests. If it is, well... there isn't much we can do, is there ? :D)
24 | - Keeps sending datagram UDP packets to each connected host on the same port range and keeps waiting for replies.
25 | - Once the services are found, you can then initialize a socket connection to those services using the awesome [react-native-udp](https://github.com/tradle/react-native-udp) package or its fork the [react-native-tcp](https://github.com/PeelTechnologies/react-native-tcp) package by [Andy Prock](https://github.com/aprock) depending on your needs.
26 |
27 | ### Install
28 | npm i -S react-native-lanscan
29 |
30 | #### Android
31 |
32 |
33 | - Add the following line to the bottom of your project's `settings.gradle` file.
34 |
35 | `project(':react-native-lanscan').projectDir = new File(settingsDir, '../node_modules/react-native-lanscan/android')`
36 |
37 | - Change the `include` line of your project's `settings.gradle` to include the `:react-native-lanscan` project.
38 |
39 | `include ':react-native-lanscan', ':app'`
40 |
41 | - Open your app's `build.gradle` file and add the following line to the `dependencies` block.
42 |
43 | `compile project(":react-native-lanscan")`
44 |
45 | - In your app's `MainActivity.java` file, add `new LANScanReactModule()` to the return statement of the `getPackages()` function.
46 |
47 | ```
48 | ...
49 | new MainReactPackage(),
50 | new LANScanReactModule()
51 | ...
52 | ```
53 |
54 | - Then in the same file add the import statement :
55 | `import com.odinvt.lanscan.LANScanReactModule;`
56 |
57 |
58 | ### Usage
59 |
60 | import { LANScan } from 'react-native-lanscan';
61 | let lanscan = new LANScan();
62 |
63 | #### API
64 |
65 | All the found hosts are returned via event emitter. You need to bind your listeners to the proper events first depending on your logic, then call the API methods.
66 |
67 | ##### Methods
68 |
69 | ###### `scan(min_port, max_port, broadcast_timeout = 500, fallback = true, icmp_timeout = 50, packet_timeout = 500)` Starts the scan for the hosts that have services in the min_port-max_port port range
70 |
71 |
72 | - This method does exactly the steps detailed in the **General Information** section.
73 | - `min_port` and `max_port` are included in the port range.
74 | - `min_port` and `max_port` must be within the port range 0-65535.
75 | - The `broadcast_timeout` **(optional)** parameter `default : 500` specifies how much time in `milliseconds` the UDP broadcast messages should wait for replies.
76 | - The `fallback` **(optional)** parameter `default : true` tells the method to fall back to DEEPSCAN if no host replied to the UDP broadcast messages.
77 | - The `icmp_timeout` **(optional)** parameter `default : 50` specifies how much time in `milliseconds` each ICMP ECHO request should wait for a reply from the host. Keep in mind that the hosts are on the local network so this shouldn't take more than 10 ms.
78 | - If a host replies to an ICMP ECHO request, the method starts sending UDP packets to the hosts on the min_port-max_port port range. The `packet_timeout` **(optional)** paramter `default : 500` tells it how much time in `milliseconds` the UDP packets should wait for replies.
79 |
80 | > The Datagram UDP Sockets send a **byte[4]** "RNLS" message with the packets and expect a **byte[6]** response message with the packets like "RNLSOK". (Check out the java service example at the last section of this README).
81 |
82 | Example :
83 |
84 | ```javascript
85 |
86 | lanscan.scan(48500, 48503, 100, true, 20, 100);
87 |
88 | /*
89 | starts the scan for all the hosts in the network
90 | that have ready UDP services on the ports : 48500, 48501, 48502 or 48503
91 | */
92 |
93 | ```
94 |
95 | ###### `fetchInfo()` Fetches the device wifi dhcp info
96 |
97 | - Use this method if you need the system to only fetch the dhcp info without starting a scan because the `scan` method already calls it.
98 | - The info are returned in the `info_fetched` event too.
99 |
100 | ###### `getConnectedHosts()` Returns the hosts that successfully replied to an ICMP ECHO request
101 |
102 | - This will return an `array` of the connected hosts.
103 | - Be sure to **only use this method if** you've set the `fallback` parameter of the `scan` call to `true`. Otherwise, it will always return an empty array as it would've never fallen back to the deepscan.
104 | - This should be called inside `end_pings` or `end` event handlers. As it is only then when you're sure that all the connected hosts list will be available.
105 |
106 | Example :
107 |
108 | ```javascript
109 |
110 | lanscan.on('end_pings', () => {
111 | let connected_hosts = lanscan.getConnectedHosts();
112 | // connected_hosts = ["192.168.1.10", "192.168.1.15"]
113 | // if 192.168.1.10 & 192.168.1.15 responded to the pings.
114 | });
115 | lanscan.scan(48500, 48503, 100, true, 20, 100);
116 |
117 | ```
118 |
119 | ###### `getAvailableHosts()` Returns the hosts that are available on the LAN and the ports that services are listening on on those hosts
120 |
121 | - This will return an `object` of the available hosts. The keys of the object are the `IP addresses` and the values are `array`s of the ports that services are listening on, on those hosts.
122 | - This should be called inside an `end` event handler. As it is only then when you're sure that all the available hosts list will be available.
123 | - You can call this inside an `end_broadcast` event handler too **but only if** you set the `fallback` parameter of the `scan` call to `false`
124 |
125 | Example :
126 |
127 | ```javascript
128 |
129 | lanscan.on('end', () => {
130 | let available_hosts = lanscan.getAvailableHosts();
131 | })
132 | lanscan.scan(48500, 48503, 100, true, 20, 100);
133 |
134 | ```
135 | `available_hosts` is equal to :
136 |
137 | {
138 | "192.168.1.10" : [48500, 48502, 48503],
139 | "192.168.1.15" : [48501],
140 | "192.168.1.18" : [48502, 48503],
141 | }
142 |
143 | if those are the hosts that have services listening on respective UDP ports. Keep in mind that those would be UDP sockets that responded to either broadcast Datagram UDP packets or host-specific Datagram UDP packets with a byte[6] message (check out the **Java Service Example** section at the end of this README).
144 |
145 |
146 | ###### `stop()` Stops all the running scan threads and cleans up the used resources on the native side
147 |
148 | - This does not clear the connectedHosts and availableHosts cached lists. If you want such behavior, you can overwrite your object with a new instance `lanscan = new LANScan()` (don't forget to bind your event listeners to the new object as those would've been destroyed).
149 |
150 | ##### Events
151 |
152 | ```javascript
153 | lanscan.on('eventName', (params...) => {
154 | ...
155 | })
156 | ```
157 |
158 | ###### `start` Triggered on scan start
159 | ###### `stop` Triggered after stop() successfully shuts down device scan threads
160 | ###### `start_fetch` Triggered when it starts fetching device dhcp info
161 | ###### `info_fetched` Triggered when the device info are fetched
162 |
163 | - Broadcasts an `object` that contains the device Wi-Fi dhcp info
164 |
165 | ```javascript
166 |
167 | lanscan.on('info_fetched', (info) => {
168 | // do something with `info`
169 | })
170 |
171 | ```
172 |
173 | {
174 | ipAddress : "192.168.1.2",
175 | netmask : "255.255.255.0",
176 | gateway : "192.168.1.1",
177 | serverAddress : "192.168.1.1",
178 | dns1 : "8.8.8.8",
179 | dns2 : "8.8.4.4",
180 | hostsNumber : "256"
181 | }
182 |
183 | ###### `start_broadcast` Triggered when the broadcast discovery starts before any packets are sent
184 | ###### `host_found` Triggered when a host is found with a service running on it
185 |
186 | - Broadcasts an `object`. This object has a `string` property named `host` which is the IP address of the found host, and an `int` property named `port` which is the port of the service that responded.
187 | - Broadcasts an `object` of the available hosts up until the current host was discovered. The keys of the object are the `IP addresses` and the values are `array`s of the ports that services are listening on, on those hosts.
188 | - Can trigger either when a host responds to a broadcast UDP packet on a port, or when a host responds to a host-specific UDP packet that was sent to it after it was discovered by an ICMP ECHO request.
189 | - You can safely use this event to update your UI and show the current found services.
190 |
191 |
192 | ```javascript
193 |
194 | lanscan.on('host_found', (host, currentAvailableHosts) => {
195 | // update my UI
196 | })
197 | lanscan.scan(48500, 48503, 100, true, 20, 100);
198 |
199 | ```
200 |
201 | `host` is equal to :
202 |
203 | {
204 | host : "192.168.1.15",
205 | port : 48501
206 | }
207 |
208 | If the service on `192.168.1.15:48501` responded to a UDP packet.
209 |
210 | `currentAvailableHosts` is equal to :
211 |
212 | {
213 | "192.168.1.10" : [48500, 48502, 48503],
214 | "192.168.1.15" : [48501]
215 | }
216 |
217 | If those are the services that it found up until the event was triggered. If it finds another service on `192.168.1.15:48502`, then, in another `host_found` event trigger, it would just add that to the ports array :
218 |
219 | {
220 | "192.168.1.10" : [48500, 48502, 48503],
221 | "192.168.1.15" : [48501, 48502]
222 | }
223 |
224 | ###### `end_broadcast` Triggered when the broadcast discovery threads finish
225 |
226 | - You can use this to call `getAvailableHosts()` **but only if** you set the `fallback` parameter of the `scan` call to `false`.
227 |
228 | ###### `start_pings` Triggered when the package starts pinging the calculated list of possible subnet IPs
229 |
230 | - This only triggers when the `scan` call doesn't find any hosts and the `fallback` parameter is set to `true`.
231 |
232 | ###### `host_found_ping` Triggered when a host responds to an ICMP ECHO request within the specified timeout
233 |
234 | - Triggers only between `start_pings` event and `end_pings` event in time.
235 | - Broadcasts a `string` of the IP address of the host that was found.
236 | - Broadcasts an `array` of the hosts that responded to ping requests up until the moment the current host was found.
237 |
238 | ```javascript
239 |
240 | lanscan.on('host_found_ping', (host, currentConnectedHosts) => {
241 | // host = "192.168.1.15"
242 | /* currentConnectedHosts = ["192.168.1.11", "192.168.1.13", "192.168.1.15"]
243 | if "192.168.1.11" and "192.168.1.13" already responded */
244 | })
245 |
246 | ```
247 |
248 | ###### `end_pings` Triggered when all the pings are finished
249 |
250 | - Do not use this to call `getAvailableHosts()` as the threads that send and receive datagram packets may still be running when this event is triggered.
251 |
252 | ###### `end` Triggered when scan stops (when all threads finish)
253 |
254 | - It is always safe to use this to call `getAvailableHosts()` and `getConnectedHosts()`
255 |
256 | ###### `port_out_of_range_error` Triggers when the specified min_port and max_port ports are out of range
257 |
258 | - Broadcasts a `string` message of the error.
259 | - This error does break the execution of the whole process.
260 |
261 | ###### `fetch_error` Triggers when fetchInfo() of the device Wi-Fi State fails
262 |
263 | - Broadcasts a `string` message of the error.
264 | - This error does break the execution of the whole process.
265 |
266 | ###### `error` Triggers whenever an error occurs during the process
267 |
268 | - This includes `port_out_of_range_error` and `fetch_error` errors.
269 | - Broadcasts a `string` message of the error.
270 | - This error does break the execution of the whole process.
271 |
272 | ### Java Service Example
273 |
274 | This is an example of a service implementation that will be discovered by the `react-native-lanscan` package.
275 |
276 | ```java
277 |
278 | import java.io.IOException;
279 | import java.net.*;
280 |
281 | public class Receiver {
282 |
283 | public static void main(String[] args) {
284 | int port = 48500;
285 | new Receiver().run(port);
286 | }
287 |
288 | public void run(int port) {
289 | DatagramSocket serverSocket = null;
290 | try {
291 | serverSocket = new DatagramSocket(port);
292 | byte[] receiveData = new byte[4];
293 |
294 | System.out.printf("Listening on udp:%s:%d%n",
295 | InetAddress.getLocalHost().getHostAddress(), port);
296 | DatagramPacket receivePacket = new DatagramPacket(receiveData,
297 | receiveData.length);
298 |
299 | while(true)
300 | {
301 | serverSocket.receive(receivePacket);
302 | String sentence = new String( receivePacket.getData(), 0,
303 | receivePacket.getLength() );
304 | System.out.println("RECEIVED: " + sentence);
305 | // now send acknowledgement packet back to sender
306 | InetAddress IPAddress = receivePacket.getAddress();
307 | String sendString = "RNLSOK";
308 | byte[] sendData = sendString.getBytes("UTF-8");
309 | DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length,
310 | IPAddress, receivePacket.getPort());
311 | serverSocket.send(sendPacket);
312 | }
313 | } catch (IOException e) {
314 | System.out.println(e);
315 | } finally {
316 | serverSocket.close();
317 | }
318 | }
319 | }
320 |
321 | ```
322 |
323 | ### Babel
324 |
325 | This component uses ES6. So if you're using `Webpack` you should launch `babel` on `main.js` and output to `main-tf.js` if for some reason the `npm postinstall` script didn't execute.
326 |
327 | "postinstall": "babel main.js --out-file main-tf.js"
328 |
--------------------------------------------------------------------------------
/android/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 |
3 | android {
4 | compileSdkVersion 23
5 | buildToolsVersion "23.0.3"
6 |
7 | defaultConfig {
8 | minSdkVersion 16
9 | targetSdkVersion 23
10 | versionCode 1
11 | versionName "1.0"
12 | }
13 | }
14 |
15 | dependencies {
16 | compile 'com.android.support:appcompat-v7:23.4.0'
17 | compile 'com.facebook.react:react-native:+'
18 | }
19 |
--------------------------------------------------------------------------------
/android/react-native-lanscan.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | generateDebugSources
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 |
124 |
125 |
126 |
127 |
128 |
--------------------------------------------------------------------------------
/android/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/android/src/main/java/com/odinvt/lanscan/LANScanModule.java:
--------------------------------------------------------------------------------
1 | package com.odinvt.lanscan;
2 |
3 | import com.facebook.react.bridge.GuardedAsyncTask;
4 | import com.facebook.react.bridge.ReactApplicationContext;
5 | import com.facebook.react.bridge.ReactContext;
6 | import com.facebook.react.bridge.ReactContextBaseJavaModule;
7 | import com.facebook.react.bridge.ReactMethod;
8 | import com.facebook.react.bridge.WritableMap;
9 | import com.facebook.react.bridge.WritableNativeMap;
10 | import com.facebook.react.modules.core.DeviceEventManagerModule;
11 | import com.odinvt.lanscan.impl.ManagedThreadPoolExecutor;
12 | import com.odinvt.lanscan.utils.IPv4;
13 |
14 | import android.content.Context;
15 | import android.net.*;
16 | import android.net.wifi.WifiManager;
17 | import android.os.AsyncTask;
18 | import android.os.SystemClock;
19 | import android.util.Log;
20 |
21 | import java.io.IOException;
22 | import java.net.DatagramPacket;
23 | import java.net.DatagramSocket;
24 | import java.net.InetAddress;
25 | import java.net.SocketException;
26 | import java.net.UnknownHostException;
27 | import java.util.ArrayList;
28 | import java.util.Date;
29 | import java.util.HashMap;
30 | import java.util.concurrent.Executor;
31 | import java.util.concurrent.ThreadPoolExecutor;
32 |
33 | import javax.annotation.Nullable;
34 |
35 | public class LANScanModule extends ReactContextBaseJavaModule {
36 | private static final int MIN_PORT_NUMBER = 0;
37 | private static final int MAX_PORT_NUMBER = 0xFFFF;
38 |
39 | private static final String KEY_WIFISTATE_DNS1 = "dns1";
40 | private static final String KEY_WIFISTATE_DNS2 = "dns2";
41 | private static final String KEY_WIFISTATE_GATEWAY = "gateway";
42 | private static final String KEY_WIFISTATE_IPADDRESS = "ipAddress";
43 | private static final String KEY_WIFISTATE_LEASEDURATION = "leaseDuration";
44 | private static final String KEY_WIFISTATE_NETMASK = "netmask";
45 | private static final String KEY_WIFISTATE_SERVERADDRESS = "serverAddress";
46 | private static final String KEY_IPv4_HOSTSNUMBER = "hostsNumber";
47 |
48 |
49 | private static final String EVENT_START = "RNLANScanStart";
50 | private static final String EVENT_STOP = "RNLANScanStop";
51 | private static final String EVENT_STARTFETCH = "RNLANScanStartFetch";
52 | private static final String EVENT_INFOFETCHED = "RNLANScanInfoFetched";
53 | private static final String EVENT_FETCHERROR = "RNLANScanFetchError";
54 | private static final String EVENT_STARTPINGS = "RNLANScanStartPings";
55 | private static final String EVENT_HOSTFOUNDPING = "RNLANScanHostFoundPing";
56 | private static final String EVENT_ENDPINGS = "RNLANScanEndPings";
57 | private static final String EVENT_PORTOUTOFRANGEERROR = "RNLANScanPortOutOfRangeError";
58 | private static final String EVENT_STARTBROADCAST = "RNLANScanStartBroadcast";
59 | private static final String EVENT_HOSTFOUND = "RNLANScanHostFound";
60 | private static final String EVENT_ENDBROADCAST = "RNLANScanEndBroadcast";
61 | private static final String EVENT_END = "RNLANScanEnd";
62 | private static final String EVENT_ERROR = "RNLANScanError";
63 |
64 | private DhcpInfo dhcp_info;
65 | private IPv4 ipv4_wifi;
66 | private ArrayList hosts_list;
67 | private HashMap> available_hosts;
68 |
69 | public LANScanModule(ReactApplicationContext reactContext) {
70 | super(reactContext);
71 | }
72 |
73 | @Override
74 | public String getName() {
75 | return "RNLANScan";
76 | }
77 |
78 | @ReactMethod
79 | public void fetchInfo(boolean force) {
80 | if(this.dhcp_info == null)
81 | this.getInfo();
82 | else if(force)
83 | this.getInfo();
84 | }
85 |
86 | @ReactMethod
87 | public void stop() {
88 | new GuardedAsyncTask(getReactApplicationContext()) {
89 | @Override
90 | protected void doInBackgroundGuarded(Void... params) {
91 | ((ThreadPoolExecutor)ManagedThreadPoolExecutor.THREAD_POOL_EXECUTOR_PINGS).shutdownNow();
92 | ((ThreadPoolExecutor)ManagedThreadPoolExecutor.THREAD_POOL_EXECUTOR_BROADCAST).shutdownNow();
93 |
94 | long startTime = System.currentTimeMillis();
95 | long endTime = 0L;
96 | long timeout = 1000;
97 | boolean isTerminated_broadcast = false;
98 | boolean isTerminated_pings = false;
99 |
100 | // wait until all the threads are terminated
101 | // or grace timeout finishes
102 | while(!isTerminated_broadcast || !isTerminated_pings || endTime < timeout) {
103 | isTerminated_broadcast = ((ThreadPoolExecutor)ManagedThreadPoolExecutor.THREAD_POOL_EXECUTOR_BROADCAST).isTerminated();
104 | isTerminated_pings = ((ThreadPoolExecutor)ManagedThreadPoolExecutor.THREAD_POOL_EXECUTOR_BROADCAST).isTerminated();
105 | endTime = (new Date()).getTime() - startTime;
106 | }
107 |
108 | // successfully stopped the tasks... send top event
109 | sendEvent(getReactApplicationContext(), EVENT_STOP, null);
110 | }
111 | }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
112 | }
113 |
114 | @ReactMethod
115 | public void scan(final int min_port, final int max_port, final int broadcast_timeout, final boolean fallback, final int ping_ms, final int port_ms) {
116 |
117 | // start event should be sent before probable errors
118 | sendEvent(getReactApplicationContext(), EVENT_START, null);
119 |
120 | if(min_port < MIN_PORT_NUMBER || min_port > MAX_PORT_NUMBER || max_port < MIN_PORT_NUMBER || max_port > MAX_PORT_NUMBER) {
121 | String err = "Port out of range";
122 | sendEvent(getReactApplicationContext(), EVENT_PORTOUTOFRANGEERROR, err);
123 | sendEvent(getReactApplicationContext(), EVENT_ERROR, err);
124 |
125 | return ;
126 | }
127 |
128 | if(this.getInfo()) {
129 | final String broadcastAddr = ipv4_wifi.getBroadcastAddress();
130 |
131 | this.available_hosts = new HashMap<>();
132 |
133 | // trigger the start broadcast event if it is a broadcast address
134 | sendEvent(getReactApplicationContext(), EVENT_STARTBROADCAST, null);
135 |
136 | for(int i = min_port; i <= max_port; i++) {
137 | sendDatagram(broadcastAddr, true, i,broadcast_timeout, ManagedThreadPoolExecutor.THREAD_POOL_EXECUTOR_BROADCAST);
138 | }
139 |
140 | ((ThreadPoolExecutor) ManagedThreadPoolExecutor.THREAD_POOL_EXECUTOR_BROADCAST).shutdown();
141 | final long timeout = broadcast_timeout + 500;
142 |
143 |
144 | //awaitTermination of the sendDatagram tasks after locking them with shutdown inside an AsyncTask (no ui framedrops)
145 | new GuardedAsyncTask(getReactApplicationContext()) {
146 | @Override
147 | protected void doInBackgroundGuarded(Void... params) {
148 |
149 | // TODO: better to use ThreadPoolExecutor.awaitTerminated (for some reason doesn't interrupt)
150 | long completed_tasks;
151 | long task_count = ((ThreadPoolExecutor) ManagedThreadPoolExecutor.THREAD_POOL_EXECUTOR_BROADCAST).getTaskCount();
152 |
153 | long startTime = System.currentTimeMillis();
154 | long endTime = 0L;
155 | // infinite loop that stops on 1 of the 2 conditions:
156 | // tasks are completed or timeout ran out
157 | while (true) {
158 | completed_tasks = ((ThreadPoolExecutor) ManagedThreadPoolExecutor.THREAD_POOL_EXECUTOR_BROADCAST).getCompletedTaskCount();
159 | //Log.wtf("WAITING FOR TASKS BROADCAST : ", "WAITING FOR TASKS TO COMPLETE " + completed_tasks + "/" + task_count);
160 | if (completed_tasks < task_count || endTime < timeout) {
161 | endTime = (new Date()).getTime() - startTime;
162 | continue;
163 | }
164 |
165 | // at this point all the tasks should be closed. send end broadcast event
166 | sendEvent(getReactApplicationContext(), EVENT_ENDBROADCAST, null);
167 |
168 |
169 | if(fallback) {
170 |
171 | //Log.wtf("FOUND DEVICES : ", "THE BROADCAST FOUND : " + available_hosts.size() + " hosts");
172 |
173 | // if no device are found and user wants to fallback to host to host port scan
174 | if (available_hosts.size() == 0) {
175 |
176 | sendEvent(getReactApplicationContext(), EVENT_STARTPINGS, null);
177 |
178 | ArrayList connected = new ArrayList<>();
179 | //Log.wtf("NETWORK", "PINGING " + hosts_list.size() + " Hosts....");
180 | String device_ip = "";
181 | try {
182 | device_ip = intToIp(dhcp_info.ipAddress);
183 | } catch (UnknownHostException e) {
184 | e.printStackTrace();
185 | }
186 | for (String host : hosts_list) {
187 | try {
188 | if (host.equals(device_ip))
189 | continue;
190 | if (InetAddress.getByName(host).isReachable(ping_ms)) {
191 | connected.add(host);
192 |
193 | //Log.wtf("HOST FOUND !!!", host + " RESPONDED");
194 | sendEvent(getReactApplicationContext(), EVENT_HOSTFOUNDPING, host);
195 |
196 | for (int i = min_port; i <= max_port; i++) {
197 | sendDatagram(host, false, i, port_ms, ManagedThreadPoolExecutor.THREAD_POOL_EXECUTOR_PINGS);
198 | }
199 | } else {
200 | //Log.wtf("HOST NOT RESPONSIVE", host + " is not responding");
201 | }
202 | } catch (IOException ioe) {
203 | /* do nothing just continue to the next host */
204 | ioe.printStackTrace();
205 | }
206 | }
207 | sendEvent(getReactApplicationContext(), EVENT_ENDPINGS, connected.size());
208 |
209 | }
210 |
211 | }
212 |
213 | // start waiting for ping UDP tasks to finish to send the end event
214 | ((ThreadPoolExecutor) ManagedThreadPoolExecutor.THREAD_POOL_EXECUTOR_PINGS).shutdown();
215 | long timeout_pings = port_ms + 500;
216 |
217 | // TODO: better to use ThreadPoolExecutor.awaitTerminated (for some reason doesn't interrupt)
218 | long completed_tasks_pings;
219 | long task_count_pings = ((ThreadPoolExecutor) ManagedThreadPoolExecutor.THREAD_POOL_EXECUTOR_PINGS).getTaskCount();
220 |
221 | long startTime_pings = System.currentTimeMillis();
222 | long endTime_pings = 0L;
223 | // wait for ping tasks
224 | // infinite loop that stops on 1 of the 2 conditions:
225 | // ping udp tasks are completed or timeout ran out
226 | while(true) {
227 | completed_tasks_pings = ((ThreadPoolExecutor) ManagedThreadPoolExecutor.THREAD_POOL_EXECUTOR_PINGS).getCompletedTaskCount();
228 | if (completed_tasks_pings < task_count_pings || endTime_pings < timeout_pings) {
229 | endTime_pings = (new Date()).getTime() - startTime_pings;
230 | continue;
231 | }
232 |
233 | // at this point all the tasks (pings or broadcast) should be closed. send end event
234 | sendEvent(getReactApplicationContext(), EVENT_END, null);
235 |
236 | break;
237 | }
238 |
239 |
240 |
241 | break;
242 | }
243 | }
244 | }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
245 |
246 |
247 | }
248 | }
249 |
250 | public void sendDatagram(final String broadcastAddr,
251 | final boolean broadcast,
252 | final int port,
253 | final long timeout_ms,
254 | Executor thread_pool) {
255 |
256 |
257 | try {
258 | final DatagramSocket serverSocket = new DatagramSocket();
259 | serverSocket.setBroadcast(broadcast);
260 | serverSocket.setReuseAddress(true);
261 | InetAddress IPAddress = InetAddress.getByName(broadcastAddr);
262 | //Log.wtf("Info", "Sending Discovery message to " + IPAddress.getHostAddress() + " Via UDP port " + port);
263 |
264 | // we're sending "RNLS" message so if you need to check on the other devices on local network
265 | // you need to open an udp listener on port 'port' and wait for the message "RNLS" which is a byte[4]
266 | byte[] sendData = new byte[4];
267 | sendData[0] = 'R';
268 | sendData[1] = 'N';
269 | sendData[2] = 'L';
270 | sendData[3] = 'S';
271 |
272 | final DatagramPacket sendPacket = new DatagramPacket(sendData,sendData.length,IPAddress,port);
273 |
274 | //Log.wtf("STARTING TASK : ", "STARTING RECEIVER TASK FOR " + broadcastAddr);
275 | // Execute a receiver task in the background to start waiting for LAN replies before sending packets
276 | final AsyncTask guarded_receive_task = new GuardedAsyncTask(getReactApplicationContext()) {
277 | @Override
278 | protected void doInBackgroundGuarded(Void... params) {
279 | byte[] receiveData = new byte[6]; // waiting for the message "RNLSOK"
280 | DatagramPacket receivePacket = new DatagramPacket(receiveData,
281 | receiveData.length);
282 |
283 | // skip if it is the current device
284 | String device_ip = "";
285 | try {
286 | device_ip = intToIp(dhcp_info.ipAddress);
287 |
288 | // try to set a timeout for the receive operation of the socket in this thread as
289 | // without this if no host responds it will hang forever
290 | serverSocket.setSoTimeout((int) timeout_ms);
291 | } catch (UnknownHostException e) {
292 | e.printStackTrace();
293 | } catch (SocketException e) {
294 | e.printStackTrace();
295 | /* if the socket fails to set a timeout for the receive operation we need to force close this thread as
296 | other waiting process depend on it completing
297 | */
298 |
299 | return ;
300 | }
301 |
302 |
303 | //noinspection InfiniteLoopStatement
304 | while(!isCancelled()) {
305 |
306 | try {
307 | //Log.wtf("NETWORK : ", "WAITING FOR DATAGRAM RESPONSE...");
308 | serverSocket.receive(receivePacket);
309 |
310 | String sentence = new String( receivePacket.getData(), 0,
311 | receivePacket.getLength() );
312 | //Log.wtf("RECEIVED PACKET : " , sentence + " FROM " + receivePacket.getAddress() + ":" + receivePacket.getPort());
313 |
314 | InetAddress address = receivePacket.getAddress();
315 | String addr = address.getHostAddress();
316 | int port = receivePacket.getPort();
317 |
318 |
319 | // skip if packet received from current device
320 | if(addr.equals(device_ip)) {
321 | //Log.wtf("SKIPING : ", "SKIPPING CURRENT DEVICE RESPONDED ....");
322 | continue;
323 | }
324 |
325 | if(available_hosts.containsKey(addr)) {
326 | if(!available_hosts.get(addr).contains(port)) {
327 | available_hosts.get(addr).add(port);
328 | }
329 | } else {
330 | ArrayList port_arr = new ArrayList<>();
331 | port_arr.add(port);
332 | available_hosts.put(addr, port_arr);
333 | }
334 |
335 | WritableMap available_host = new WritableNativeMap();
336 | available_host.putString("host", addr);
337 | available_host.putInt("port", port);
338 | sendEvent(getReactApplicationContext(), EVENT_HOSTFOUND, available_host);
339 | //Log.wtf("NETWORK : ", "LOOPING BACK FOR THE NEXT DATAGRAM RECEIVE...");
340 | } catch (IOException e) {
341 | Log.e("IOE", e.getMessage());
342 | /* cancel current task if can't listen for packets */
343 | this.cancel(true);
344 | }
345 | }
346 | }
347 |
348 | @Override
349 | protected void onCancelled() {
350 | //Log.wtf("CLOSING TASK : ", "CLOSING RECEIVER TASK FOR " + broadcastAddr);
351 |
352 | // at this point the socket is not used anymore so we can go ahead and close it
353 | serverSocket.disconnect();
354 | serverSocket.close();
355 | }
356 | }.executeOnExecutor(thread_pool);
357 |
358 | //Log.wtf("STARTING TASK : ", "STARTING SENDER TASK FOR " + broadcastAddr);
359 | // start sending packets on a background task until it is cancelled then trigger end broadcast event
360 | // if it is a broadcast address
361 | final AsyncTask guarded_send_task = new GuardedAsyncTask(getReactApplicationContext()) {
362 | @Override
363 | protected void doInBackgroundGuarded(Void... params) {
364 |
365 |
366 | while(!isCancelled()) {
367 | try {
368 | ////Log.wtf("SENDING PACKET : " , sendPacket.getData().toString() + " TO " + broadcastAddr + ":" + port);
369 | serverSocket.send(sendPacket);
370 | } catch (IOException e) {
371 | /* do nothing just continue the loop to send the next packet */
372 | Log.e("ERROR SENDING PACKETS " , e.getMessage());
373 | }
374 | }
375 | // when we exit the loop it means that the task has been cancelled and we're not sending packets anymore
376 |
377 | //Log.wtf("CLOSING TASK : ", "TRYING TO CLOSE RECEIVER TASK FROM SENDER FOR " + broadcastAddr);
378 | // shutdown the receiver task since we're not gonna be needing it anymore
379 | if(guarded_receive_task != null)
380 | guarded_receive_task.cancel(true);
381 | }
382 |
383 | @Override
384 | protected void onCancelled() {
385 | //Log.wtf("CLOSING TASK : ", "CLOSING SENDER TASK FOR " + broadcastAddr);
386 |
387 | }
388 | }.executeOnExecutor(thread_pool);
389 |
390 | // run a sleep task on the background to wait for the timeout
391 | new GuardedAsyncTask(getReactApplicationContext()) {
392 | @Override
393 | protected void doInBackgroundGuarded(Void... params) {
394 | SystemClock.sleep(timeout_ms);
395 | //Log.wtf("WAITED TIMEOUT : ", "WAIT FOR TIMEOUT FINISHED TRYING TO CLOSE SENDER TASK FOR " + broadcastAddr + "...");
396 | if(guarded_send_task != null)
397 | guarded_send_task.cancel(true);
398 | this.cancel(true);
399 | }
400 | }.executeOnExecutor(thread_pool);
401 |
402 |
403 |
404 | } catch (SocketException | UnknownHostException e) {
405 | e.printStackTrace();
406 | }
407 | finally {
408 | /* should not be closing datagram socket because it's still used asynchronously by threads */
409 | }
410 |
411 |
412 | }
413 |
414 | private boolean getInfo() {
415 | sendEvent(getReactApplicationContext(), EVENT_STARTFETCH, null);
416 |
417 | WifiManager wifi_manager= (WifiManager) getReactApplicationContext().getSystemService(Context.WIFI_SERVICE);
418 | dhcp_info=wifi_manager.getDhcpInfo();
419 |
420 | try {
421 | String s_dns1 = intToIp(dhcp_info.dns1);
422 | String s_dns2 = intToIp(dhcp_info.dns2);
423 | String s_gateway = intToIp(dhcp_info.gateway);
424 | String s_ipAddress = intToIp(dhcp_info.ipAddress);
425 | int s_leaseDuration = dhcp_info.leaseDuration;
426 | String s_netmask = intToIp(dhcp_info.netmask);
427 | String s_serverAddress = intToIp(dhcp_info.serverAddress);
428 |
429 | WritableMap device_info = new WritableNativeMap();
430 | device_info.putString(KEY_WIFISTATE_DNS1, s_dns1);
431 | device_info.putString(KEY_WIFISTATE_DNS2, s_dns2);
432 | device_info.putString(KEY_WIFISTATE_GATEWAY, s_gateway);
433 | device_info.putString(KEY_WIFISTATE_IPADDRESS, s_ipAddress);
434 | device_info.putInt(KEY_WIFISTATE_LEASEDURATION, s_leaseDuration);
435 | device_info.putString(KEY_WIFISTATE_NETMASK, s_netmask);
436 | device_info.putString(KEY_WIFISTATE_SERVERADDRESS, s_serverAddress);
437 |
438 | ipv4_wifi = new IPv4(s_ipAddress, s_netmask);
439 | hosts_list = ipv4_wifi.getHostAddressList();
440 |
441 | device_info.putInt(KEY_IPv4_HOSTSNUMBER, ipv4_wifi.getNumberOfHosts().intValue());
442 |
443 | sendEvent(getReactApplicationContext(), EVENT_INFOFETCHED, device_info);
444 |
445 | return true;
446 | } catch (UnknownHostException e) {
447 |
448 | sendEvent(getReactApplicationContext(), EVENT_FETCHERROR, e.getMessage());
449 | sendEvent(getReactApplicationContext(), EVENT_ERROR, e.getMessage());
450 | }
451 |
452 | return false;
453 |
454 | }
455 |
456 | public String intToIp(int i) throws UnknownHostException {
457 |
458 | byte[] addressBytes = { (byte)(0xff & i),
459 | (byte)(0xff & (i >> 8)),
460 | (byte)(0xff & (i >> 16)),
461 | (byte)(0xff & (i >> 24)) };
462 |
463 | InetAddress addr = InetAddress.getByAddress(addressBytes);
464 |
465 | return addr.getHostAddress();
466 | }
467 |
468 | protected void sendEvent(ReactContext reactContext,
469 | String eventName,
470 | @Nullable Object params) {
471 | reactContext
472 | .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
473 | .emit(eventName, params);
474 | //Log.wtf("EVENT : ", "TRIGGERED EVENT " + eventName);
475 | }
476 |
477 | }
478 |
--------------------------------------------------------------------------------
/android/src/main/java/com/odinvt/lanscan/LANScanReactModule.java:
--------------------------------------------------------------------------------
1 | package com.odinvt.lanscan;
2 |
3 | import com.facebook.react.ReactPackage;
4 | import com.facebook.react.bridge.JavaScriptModule;
5 | import com.facebook.react.bridge.NativeModule;
6 | import com.facebook.react.bridge.ReactApplicationContext;
7 | import com.facebook.react.uimanager.ViewManager;
8 |
9 | import java.util.ArrayList;
10 | import java.util.Collections;
11 | import java.util.List;
12 |
13 | public class LANScanReactModule implements ReactPackage {
14 |
15 | @Override
16 | public List> createJSModules() {
17 | return Collections.emptyList();
18 | }
19 |
20 | @Override
21 | public List createViewManagers(ReactApplicationContext reactContext) {
22 | return Collections.emptyList();
23 | }
24 |
25 | @Override
26 | public List createNativeModules(
27 | ReactApplicationContext reactContext) {
28 | List modules = new ArrayList<>();
29 |
30 | modules.add(new LANScanModule(reactContext));
31 |
32 | return modules;
33 | }
34 | }
--------------------------------------------------------------------------------
/android/src/main/java/com/odinvt/lanscan/impl/ManagedThreadPoolExecutor.java:
--------------------------------------------------------------------------------
1 | package com.odinvt.lanscan.impl;
2 |
3 |
4 | import android.support.annotation.NonNull;
5 |
6 | import java.util.concurrent.BlockingQueue;
7 | import java.util.concurrent.Executor;
8 | import java.util.concurrent.LinkedBlockingQueue;
9 | import java.util.concurrent.ThreadFactory;
10 | import java.util.concurrent.ThreadPoolExecutor;
11 | import java.util.concurrent.TimeUnit;
12 | import java.util.concurrent.atomic.AtomicInteger;
13 |
14 | public class ManagedThreadPoolExecutor {
15 | private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors();
16 | private static final int CORE_POOL_SIZE = CPU_COUNT + 1;
17 | private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1;
18 | private static final int KEEP_ALIVE = 1;
19 | private static final BlockingQueue sPoolWorkQueue =
20 | new LinkedBlockingQueue<>(128);
21 | private static final ThreadFactory sThreadFactory = new ThreadFactory() {
22 | private final AtomicInteger mCount = new AtomicInteger(1);
23 |
24 | public Thread newThread(@NonNull Runnable r) {
25 | return new Thread(r, "AsyncTask #" + mCount.getAndIncrement());
26 | }
27 | };
28 |
29 | public static final Executor THREAD_POOL_EXECUTOR_BROADCAST = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,
30 | TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);
31 | public static final Executor THREAD_POOL_EXECUTOR_PINGS = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE,
32 | TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory);
33 | }
34 |
--------------------------------------------------------------------------------
/android/src/main/java/com/odinvt/lanscan/utils/IPv4.java:
--------------------------------------------------------------------------------
1 | package com.odinvt.lanscan.utils;
2 |
3 | import java.util.ArrayList;
4 | import java.util.List;
5 |
6 | /**
7 | * Credits to Nidhish Krishnan
8 | */
9 | public class IPv4 {
10 | int baseIPnumeric;
11 | int netmaskNumeric;
12 | public IPv4(String symbolicIP, String netmask) throws NumberFormatException {
13 |
14 | /* IP */
15 | String[] st = symbolicIP.split("\\.");
16 |
17 | if (st.length != 4)
18 | throw new NumberFormatException("Invalid IP address: " + symbolicIP);
19 |
20 | int i = 24;
21 | baseIPnumeric = 0;
22 |
23 | for (int n = 0; n < st.length; n++) {
24 |
25 | int value = Integer.parseInt(st[n]);
26 |
27 | if (value != (value & 0xff)) {
28 |
29 | throw new NumberFormatException("Invalid IP address: "+ symbolicIP);
30 | }
31 |
32 | baseIPnumeric += value << i;
33 | i -= 8;
34 | }
35 |
36 | /* Netmask */
37 | st = netmask.split("\\.");
38 |
39 | if (st.length != 4)
40 | throw new NumberFormatException("Invalid netmask address: "
41 |
42 | + netmask);
43 |
44 | i = 24;
45 | netmaskNumeric = 0;
46 |
47 | if (Integer.parseInt(st[0]) < 255) {
48 |
49 | throw new NumberFormatException(
50 | "The first byte of netmask can not be less than 255");
51 | }
52 | for (int n = 0; n < st.length; n++) {
53 |
54 | int value = Integer.parseInt(st[n]);
55 |
56 | if (value != (value & 0xff)) {
57 |
58 | throw new NumberFormatException("Invalid netmask address: " + netmask);
59 | }
60 |
61 | netmaskNumeric += value << i;
62 | i -= 8;
63 |
64 | }
65 | /*
66 | * see if there are zeroes inside netmask, like: 1111111101111 This is
67 | * illegal, throw exception if encountered. Netmask should always have
68 | * only ones, then only zeroes, like: 11111111110000
69 | */
70 | boolean encounteredOne = false;
71 | int ourMaskBitPattern = 1;
72 |
73 | for (i = 0; i < 32; i++) {
74 |
75 | if ((netmaskNumeric & ourMaskBitPattern) != 0) {
76 |
77 | encounteredOne = true; // the bit is 1
78 | } else { // the bit is 0
79 | if (encounteredOne == true)
80 |
81 | throw new NumberFormatException("Invalid netmask: " + netmask + " (bit " + (i + 1) + ")");
82 | }
83 |
84 | ourMaskBitPattern = ourMaskBitPattern << 1;
85 | }
86 | }
87 |
88 | /**
89 | * Specify IP in CIDR format like: new IPv4("10.1.0.25/16");
90 | *
91 | *@param IPinCIDRFormat
92 | */
93 | public IPv4(String IPinCIDRFormat) throws NumberFormatException {
94 |
95 | String[] st = IPinCIDRFormat.split("\\/");
96 | if (st.length != 2)
97 |
98 | throw new NumberFormatException("Invalid CIDR format '"
99 | + IPinCIDRFormat + "', should be: xx.xx.xx.xx/xx");
100 |
101 | String symbolicIP = st[0];
102 | String symbolicCIDR = st[1];
103 |
104 | Integer numericCIDR = new Integer(symbolicCIDR);
105 | if (numericCIDR > 32)
106 |
107 | throw new NumberFormatException("CIDR can not be greater than 32");
108 |
109 | /* IP */
110 | st = symbolicIP.split("\\.");
111 |
112 | if (st.length != 4)
113 | throw new NumberFormatException("Invalid IP address: " + symbolicIP);
114 |
115 | int i = 24;
116 | baseIPnumeric = 0;
117 |
118 | for (int n = 0; n < st.length; n++) {
119 |
120 | int value = Integer.parseInt(st[n]);
121 |
122 | if (value != (value & 0xff)) {
123 |
124 | throw new NumberFormatException("Invalid IP address: " + symbolicIP);
125 | }
126 |
127 | baseIPnumeric += value << i;
128 | i -= 8;
129 |
130 | }
131 |
132 | /* netmask from CIDR */
133 | if (numericCIDR < 8)
134 | throw new NumberFormatException("Netmask CIDR can not be less than 8");
135 | netmaskNumeric = 0xffffffff;
136 | netmaskNumeric = netmaskNumeric << (32 - numericCIDR);
137 |
138 | }
139 |
140 | /**
141 | * Get the IP in symbolic form, i.e. xxx.xxx.xxx.xxx
142 | *
143 | *@return
144 | */
145 | public String getIP() {
146 | return convertNumericIpToSymbolic(baseIPnumeric);
147 |
148 | }
149 |
150 | private String convertNumericIpToSymbolic(Integer ip) {
151 | StringBuffer sb = new StringBuffer(15);
152 |
153 | for (int shift = 24; shift > 0; shift -= 8) {
154 |
155 | // process 3 bytes, from high order byte down.
156 | sb.append(Integer.toString((ip >>> shift) & 0xff));
157 |
158 | sb.append('.');
159 | }
160 | sb.append(Integer.toString(ip & 0xff));
161 |
162 | return sb.toString();
163 | }
164 |
165 | /**
166 | * Get the net mask in symbolic form, i.e. xxx.xxx.xxx.xxx
167 | *
168 | *@return
169 | */
170 |
171 | public String getNetmask() {
172 | StringBuffer sb = new StringBuffer(15);
173 |
174 | for (int shift = 24; shift > 0; shift -= 8) {
175 |
176 | // process 3 bytes, from high order byte down.
177 | sb.append(Integer.toString((netmaskNumeric >>> shift) & 0xff));
178 |
179 | sb.append('.');
180 | }
181 | sb.append(Integer.toString(netmaskNumeric & 0xff));
182 |
183 | return sb.toString();
184 | }
185 |
186 | /**
187 | * Get the IP and netmask in CIDR form, i.e. xxx.xxx.xxx.xxx/xx
188 | *
189 | *@return
190 | */
191 |
192 | public String getCIDR() {
193 | int i;
194 | for (i = 0; i < 32; i++) {
195 |
196 | if ((netmaskNumeric << i) == 0)
197 | break;
198 |
199 | }
200 | return convertNumericIpToSymbolic(baseIPnumeric & netmaskNumeric) + "/" + i;
201 | }
202 |
203 | /**
204 | * Get an arry of all the IP addresses available for the IP and netmask/CIDR
205 | * given at initialization
206 | *
207 | *@return
208 | */
209 | public List getAvailableIPs(Integer numberofIPs) {
210 |
211 | ArrayList result = new ArrayList();
212 | int numberOfBits;
213 |
214 | for (numberOfBits = 0; numberOfBits < 32; numberOfBits++) {
215 |
216 | if ((netmaskNumeric << numberOfBits) == 0)
217 | break;
218 |
219 | }
220 | Integer numberOfIPs = 0;
221 | for (int n = 0; n < (32 - numberOfBits); n++) {
222 |
223 | numberOfIPs = numberOfIPs << 1;
224 | numberOfIPs = numberOfIPs | 0x01;
225 |
226 | }
227 |
228 | Integer baseIP = baseIPnumeric & netmaskNumeric;
229 |
230 | for (int i = 1; i < (numberOfIPs) && i < numberofIPs; i++) {
231 |
232 | Integer ourIP = baseIP + i;
233 |
234 | String ip = convertNumericIpToSymbolic(ourIP);
235 |
236 | result.add(ip);
237 | }
238 | return result;
239 | }
240 |
241 | /**
242 | * Range of hosts
243 | *
244 | *@return
245 | */
246 | public String getHostAddressRange() {
247 |
248 | int numberOfBits;
249 | for (numberOfBits = 0; numberOfBits < 32; numberOfBits++) {
250 |
251 | if ((netmaskNumeric << numberOfBits) == 0)
252 | break;
253 | }
254 | Integer numberOfIPs = 0;
255 | for (int n = 0; n < (32 - numberOfBits); n++) {
256 |
257 | numberOfIPs = numberOfIPs << 1;
258 | numberOfIPs = numberOfIPs | 0x01;
259 |
260 | }
261 |
262 | Integer baseIP = baseIPnumeric & netmaskNumeric;
263 | String firstIP = convertNumericIpToSymbolic(baseIP + 1);
264 | String lastIP = convertNumericIpToSymbolic(baseIP + numberOfIPs - 1);
265 | return firstIP + " - " + lastIP;
266 | }
267 |
268 | /**
269 | * ArrayList of Hosts
270 | *
271 | *@return
272 | */
273 | public ArrayList getHostAddressList() {
274 |
275 | int numberOfBits;
276 | for (numberOfBits = 0; numberOfBits < 32; numberOfBits++) {
277 |
278 | if ((netmaskNumeric << numberOfBits) == 0)
279 | break;
280 | }
281 | Integer numberOfIPs = 0;
282 | for (int n = 0; n < (32 - numberOfBits); n++) {
283 |
284 | numberOfIPs = numberOfIPs << 1;
285 | numberOfIPs = numberOfIPs | 0x01;
286 |
287 | }
288 |
289 | Integer baseIP = baseIPnumeric & netmaskNumeric;
290 | ArrayList ips = new ArrayList<>();
291 | for(int i = 1; i <= numberOfIPs - 1; i++) {
292 | if(baseIPnumeric == baseIP + i)
293 | continue;
294 |
295 | ips.add(convertNumericIpToSymbolic(baseIP + i));
296 | }
297 | return ips;
298 | }
299 |
300 | /**
301 | * Returns number of hosts available in given range
302 | *
303 | *@return number of hosts
304 | */
305 | public Long getNumberOfHosts() {
306 | int numberOfBits;
307 |
308 | for (numberOfBits = 0; numberOfBits < 32; numberOfBits++) {
309 |
310 | if ((netmaskNumeric << numberOfBits) == 0)
311 | break;
312 |
313 | }
314 |
315 | Double x = Math.pow(2, (32 - numberOfBits));
316 |
317 | if (x == -1)
318 | x = 1D;
319 |
320 | return x.longValue();
321 | }
322 |
323 | /**
324 | * The XOR of the netmask
325 | *
326 | *@return wildcard mask in text form, i.e. 0.0.15.255
327 | */
328 |
329 | public String getWildcardMask() {
330 | Integer wildcardMask = netmaskNumeric ^ 0xffffffff;
331 |
332 | StringBuffer sb = new StringBuffer(15);
333 | for (int shift = 24; shift > 0; shift -= 8) {
334 |
335 | // process 3 bytes, from high order byte down.
336 | sb.append(Integer.toString((wildcardMask >>> shift) & 0xff));
337 |
338 | sb.append('.');
339 | }
340 | sb.append(Integer.toString(wildcardMask & 0xff));
341 |
342 | return sb.toString();
343 |
344 | }
345 |
346 | public String getBroadcastAddress() {
347 |
348 | if (netmaskNumeric == 0xffffffff)
349 | return "0.0.0.0";
350 |
351 | int numberOfBits;
352 | for (numberOfBits = 0; numberOfBits < 32; numberOfBits++) {
353 |
354 | if ((netmaskNumeric << numberOfBits) == 0)
355 | break;
356 |
357 | }
358 | Integer numberOfIPs = 0;
359 | for (int n = 0; n < (32 - numberOfBits); n++) {
360 |
361 | numberOfIPs = numberOfIPs << 1;
362 | numberOfIPs = numberOfIPs | 0x01;
363 | }
364 |
365 | Integer baseIP = baseIPnumeric & netmaskNumeric;
366 | Integer ourIP = baseIP + numberOfIPs;
367 |
368 | String ip = convertNumericIpToSymbolic(ourIP);
369 |
370 | return ip;
371 | }
372 |
373 | private String getBinary(Integer number) {
374 | String result = "";
375 |
376 | Integer ourMaskBitPattern = 1;
377 | for (int i = 1; i <= 32; i++) {
378 |
379 | if ((number & ourMaskBitPattern) != 0) {
380 |
381 | result = "1" + result; // the bit is 1
382 | } else { // the bit is 0
383 |
384 | result = "0" + result;
385 | }
386 | if ((i % 8) == 0 && i != 0 && i != 32)
387 |
388 | result = "." + result;
389 | ourMaskBitPattern = ourMaskBitPattern << 1;
390 |
391 | }
392 | return result;
393 | }
394 |
395 | public String getNetmaskInBinary() {
396 |
397 | return getBinary(netmaskNumeric);
398 | }
399 |
400 | /**
401 | * Checks if the given IP address contains in subnet
402 | *
403 | *@param IPaddress
404 | *@return
405 | */
406 | public boolean contains(String IPaddress) {
407 |
408 | Integer checkingIP = 0;
409 | String[] st = IPaddress.split("\\.");
410 |
411 | if (st.length != 4)
412 | throw new NumberFormatException("Invalid IP address: " + IPaddress);
413 |
414 | int i = 24;
415 | for (int n = 0; n < st.length; n++) {
416 |
417 | int value = Integer.parseInt(st[n]);
418 |
419 | if (value != (value & 0xff)) {
420 |
421 | throw new NumberFormatException("Invalid IP address: "
422 | + IPaddress);
423 | }
424 |
425 | checkingIP += value << i;
426 | i -= 8;
427 | }
428 |
429 | if ((baseIPnumeric & netmaskNumeric) == (checkingIP & netmaskNumeric))
430 |
431 | return true;
432 | else
433 | return false;
434 | }
435 |
436 | public boolean contains(IPv4 child) {
437 |
438 | Integer subnetID = child.baseIPnumeric;
439 |
440 | Integer subnetMask = child.netmaskNumeric;
441 |
442 | if ((subnetID & this.netmaskNumeric) == (this.baseIPnumeric & this.netmaskNumeric)) {
443 |
444 | if ((this.netmaskNumeric < subnetMask) == true
445 | && this.baseIPnumeric <= subnetID) {
446 |
447 | return true;
448 | }
449 |
450 | }
451 | return false;
452 |
453 | }
454 | }
455 |
--------------------------------------------------------------------------------
/main.js:
--------------------------------------------------------------------------------
1 | import RN, { NativeModules, DeviceEventEmitter } from 'react-native';
2 | import { EventEmitter } from 'events'
3 | EventEmitter.defaultMaxListeners = Infinity;
4 |
5 | let RNLANScan = NativeModules.RNLANScan;
6 |
7 | export class LANScan extends EventEmitter {
8 |
9 | static listenerCount = 0;
10 |
11 | _connectedHosts;
12 | _availableHosts;
13 |
14 | constructor(props) {
15 | super(props);
16 |
17 | this._connectedHosts = [];
18 | this._availableHosts = {};
19 |
20 | if(LANScan.listenerCount && LANScan.listenerCount > 0) {
21 | DeviceEventEmitter.removeAllListeners('RNLANScanStart');
22 | DeviceEventEmitter.removeAllListeners('RNLANScanStop');
23 | DeviceEventEmitter.removeAllListeners('RNLANScanStartFetch');
24 | DeviceEventEmitter.removeAllListeners('RNLANScanInfoFetched');
25 | DeviceEventEmitter.removeAllListeners('RNLANScanFetchError');
26 | DeviceEventEmitter.removeAllListeners('RNLANScanStartPings');
27 | DeviceEventEmitter.removeAllListeners('RNLANScanHostFoundPing');
28 | DeviceEventEmitter.removeAllListeners('RNLANScanEndPings');
29 | DeviceEventEmitter.removeAllListeners('RNLANScanPortOutOfRangeError');
30 | DeviceEventEmitter.removeAllListeners('RNLANScanStartBroadcast');
31 | DeviceEventEmitter.removeAllListeners('RNLANScanHostFound');
32 | DeviceEventEmitter.removeAllListeners('RNLANScanEndBroadcast');
33 | DeviceEventEmitter.removeAllListeners('RNLANScanEnd');
34 | DeviceEventEmitter.removeAllListeners('RNLANScanError');
35 |
36 | LANScan.listenerCount = 0;
37 | }
38 |
39 | if(LANScan.listenerCount === 0) {
40 |
41 | DeviceEventEmitter.addListener('RNLANScanStart', () => this.emit('start'));
42 | DeviceEventEmitter.addListener('RNLANScanStop', () => this.emit('stop'));
43 | DeviceEventEmitter.addListener('RNLANScanStartFetch', () => this.emit('start_fetch'));
44 | DeviceEventEmitter.addListener('RNLANScanInfoFetched', (info) => {
45 | if(!info || !info.ipAddress || !info.netmask) {
46 | this.emit('fetch_error', "No Info fetched from the device wifi state");
47 | return false;
48 | }
49 |
50 | this.emit('info_fetched', info);
51 | });
52 | DeviceEventEmitter.addListener('RNLANScanFetchError', (msg) => this.emit('fetch_error', msg));
53 | DeviceEventEmitter.addListener('RNLANScanStartPings', () => this.emit('start_pings'));
54 | DeviceEventEmitter.addListener('RNLANScanHostFoundPing', (host) => {
55 |
56 | if(!host)
57 | return false;
58 |
59 | this._connectedHosts = [...this._connectedHosts, host];
60 |
61 | this.emit('host_found_ping', host, this._connectedHosts);
62 | });
63 | DeviceEventEmitter.addListener('RNLANScanEndPings', (number) => {
64 |
65 | if(number === null)
66 | return false;
67 |
68 | this.emit('end_pings', number);
69 | });
70 | DeviceEventEmitter.addListener('RNLANScanPortOutOfRangeError', (msg) => this.emit('port_out_of_range_error', msg));
71 | DeviceEventEmitter.addListener('RNLANScanStartBroadcast', () => this.emit('start_broadcast'));
72 | DeviceEventEmitter.addListener('RNLANScanHostFound', (host) => {
73 |
74 | if(!host || !host.host || typeof host.host != "string" || host.port === null)
75 | return false;
76 |
77 | if(this._availableHosts.hasOwnProperty(host.host)) {
78 | if(this._availableHosts[host.host].indexOf(host.port) == -1) {
79 | this._availableHosts[host.host].push(host.port);
80 | }
81 | } else {
82 | this._availableHosts[host.host] = [host.port];
83 | }
84 |
85 | this.emit('host_found', host, this._availableHosts);
86 | });
87 | DeviceEventEmitter.addListener('RNLANScanEndBroadcast', () => this.emit('end_broadcast'));
88 | DeviceEventEmitter.addListener('RNLANScanEnd', () => this.emit('end'));
89 | DeviceEventEmitter.addListener('RNLANScanError', (msg) => this.emit('error', msg));
90 |
91 | }
92 |
93 |
94 | }
95 |
96 | scan(min_port, max_port, broadcast_timeout = 500, fallback = true, ping_ms = 50, port_ms = 500) {
97 | RNLANScan.scan(min_port, max_port, broadcast_timeout, fallback, ping_ms, port_ms);
98 | }
99 |
100 | stop() {
101 | RNLANScan.stop();
102 | }
103 |
104 | fetchInfo() {
105 | RNLANScan.fetchInfo();
106 | }
107 |
108 | getConnectedHosts() {
109 | return this._connectedHosts;
110 | }
111 |
112 | getAvailableHosts() {
113 | return this._availableHosts;
114 | }
115 |
116 |
117 |
118 | }
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-native-lanscan",
3 | "version": "1.0.3",
4 | "description": "A React Native library for scanning LAN IPs and ports to find services via device Wi-Fi",
5 | "main": "main-tf.js",
6 | "scripts": {
7 | "postinstall": "babel main.js --out-file main-tf.js"
8 | },
9 | "repository": {
10 | "type": "git",
11 | "url": "git+https://github.com/Odinvt/react-native-lanscan.git"
12 | },
13 | "dependencies": {
14 | "babel-cli": "^6.9.0",
15 | "babel-core": "^6.9.1",
16 | "babel-plugin-transform-es2015-constants": "^6.1.4",
17 | "babel-preset-airbnb": "^2.1.1",
18 | "babel-preset-react-native": "^1.9.0"
19 | },
20 | "devDependencies": {
21 | "babel-eslint": "^4.1.6",
22 | "eslint": "^1.10.3",
23 | "eslint-config-zavatta": "^2.0.2"
24 | },
25 | "keywords": [
26 | "react",
27 | "native",
28 | "react-native",
29 | "lanscan",
30 | "lan",
31 | "scan",
32 | "port",
33 | "wifi"
34 | ],
35 | "author": "Oussama El Bacha",
36 | "license": "MIT",
37 | "bugs": {
38 | "url": "https://github.com/Odinvt/react-native-lanscan/issues"
39 | },
40 | "homepage": "https://github.com/Odinvt/react-native-lanscan#readme"
41 | }
42 |
--------------------------------------------------------------------------------