Skip to content

Commit 118a4ba

Browse files
committed
Added subnet device scanning
1 parent b2031d9 commit 118a4ba

File tree

8 files changed

+314
-19
lines changed

8 files changed

+314
-19
lines changed

app/src/main/java/com/stealthcotper/networktools/MainActivity.java

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,17 @@
1414
import android.widget.TextView;
1515

1616
import com.stealthcopter.networktools.ARPInfo;
17+
import com.stealthcopter.networktools.IPTools;
1718
import com.stealthcopter.networktools.Ping;
1819
import com.stealthcopter.networktools.PortScan;
20+
import com.stealthcopter.networktools.SubnetDevices;
1921
import com.stealthcopter.networktools.WakeOnLan;
2022
import com.stealthcopter.networktools.ping.PingResult;
2123
import com.stealthcopter.networktools.ping.PingStats;
24+
import com.stealthcopter.networktools.subnet.SubnetInfo;
2225

2326
import java.io.IOException;
27+
import java.net.InetAddress;
2428
import java.util.ArrayList;
2529

2630
public class MainActivity extends AppCompatActivity {
@@ -38,6 +42,11 @@ protected void onCreate(Bundle savedInstanceState) {
3842
resultText = (TextView) findViewById(R.id.resultText);
3943
editIpAddress = (EditText) findViewById(R.id.editIpAddress);
4044

45+
InetAddress ipAddress = IPTools.getLocalIPv4Address();
46+
if (ipAddress != null){
47+
editIpAddress.setText(ipAddress.toString());
48+
}
49+
4150
findViewById(R.id.pingButton).setOnClickListener(new View.OnClickListener() {
4251
@Override
4352
public void onClick(View v) {
@@ -86,8 +95,23 @@ public void run() {
8695
}
8796
});
8897

89-
}
98+
findViewById(R.id.subnetDevicesButton).setOnClickListener(new View.OnClickListener() {
99+
@Override
100+
public void onClick(View v) {
101+
new Thread(new Runnable() {
102+
@Override
103+
public void run() {
104+
try {
105+
findSubnetDevices();
106+
} catch (Exception e) {
107+
e.printStackTrace();
108+
}
109+
}
110+
}).start();
111+
}
112+
});
90113

114+
}
91115

92116
private void appendResultsText(final String text) {
93117
runOnUiThread(new Runnable() {
@@ -146,7 +170,7 @@ private void doWakeOnLan() throws IllegalArgumentException {
146170
String macAddress = ARPInfo.getMACFromIPAddress(ipAddress);
147171

148172
if (macAddress == null) {
149-
appendResultsText("Could not find MAC address, cannot send WOL packet without it.");
173+
appendResultsText("Could not fromIPAddress MAC address, cannot send WOL packet without it.");
150174
return;
151175
}
152176

@@ -187,9 +211,32 @@ public void onFinished(ArrayList<Integer> openPorts) {
187211
}
188212
});
189213

214+
}
215+
216+
217+
private void findSubnetDevices() {
218+
219+
final long startTimeMillis = System.currentTimeMillis();
220+
221+
String partialIpAddress = editIpAddress.getText().toString();
222+
223+
SubnetDevices.fromLocalAddress().findDevices(new SubnetDevices.OnSubnetDeviceFound() {
224+
@Override
225+
public void onDeviceFound(SubnetInfo subnetInfo) {
226+
appendResultsText("Device: " + subnetInfo.ip+" "+subnetInfo.hostname);
227+
}
228+
229+
@Override
230+
public void onFinished(ArrayList<SubnetInfo> devicesFound) {
231+
float timeTaken = (System.currentTimeMillis() - startTimeMillis)/1000.0f;
232+
appendResultsText("Finished "+timeTaken+" s");
233+
}
234+
});
190235

191236
}
192237

238+
239+
193240
@Override
194241
public boolean onCreateOptionsMenu(Menu menu) {
195242
MenuInflater inflater = getMenuInflater();

app/src/main/res/layout/content_main.xml

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,7 @@
3636
android:layout_width="match_parent"
3737
android:layout_height="wrap_content"
3838
android:hint="@string/hint_ip_address"
39-
android:inputType="textNoSuggestions"
40-
android:text="192.168.0.1"/>
39+
android:inputType="textNoSuggestions"/>
4140
</android.support.design.widget.TextInputLayout>
4241

4342
<LinearLayout
@@ -47,25 +46,42 @@
4746

4847
<Button
4948
android:id="@+id/pingButton"
50-
android:layout_width="wrap_content"
49+
android:layout_width="0dp"
5150
android:layout_height="wrap_content"
51+
android:layout_weight="1"
5252
android:text="@string/ping"
5353
/>
5454

5555
<Button
5656
android:id="@+id/wolButton"
57-
android:layout_width="wrap_content"
57+
android:layout_width="0dp"
5858
android:layout_height="wrap_content"
59+
android:layout_weight="1"
5960
android:text="@string/wol"
6061
/>
6162
<Button
6263
android:id="@+id/portScanButton"
63-
android:layout_width="wrap_content"
64+
android:layout_width="0dp"
6465
android:layout_height="wrap_content"
66+
android:layout_weight="1"
6567
android:text="@string/port_scan"
6668
/>
6769
</LinearLayout>
6870

71+
<LinearLayout
72+
android:layout_width="match_parent"
73+
android:layout_height="wrap_content"
74+
android:orientation="horizontal">
75+
76+
<Button
77+
android:id="@+id/subnetDevicesButton"
78+
android:layout_width="wrap_content"
79+
android:layout_height="wrap_content"
80+
android:text="@string/subnet"
81+
/>
82+
83+
</LinearLayout>
84+
6985
</LinearLayout>
7086

7187
<ScrollView

app/src/main/res/values/strings.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,5 @@
77
<string name="port_scan">Port Scan</string>
88
<string name="github_page">Github</string>
99
<string name="github_url">https://github.com/stealthcopter/AndroidNetworkTools</string>
10+
<string name="subnet">Subnet Devices</string>
1011
</resources>

library/src/main/java/com/stealthcopter/networktools/ARPInfo.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
/**
1313
* Created by mat on 09/12/15.
1414
* <p/>
15-
* Looks at the file at /proc/net/arp to find ip/mac addresses from the cache
15+
* Looks at the file at /proc/net/arp to fromIPAddress ip/mac addresses from the cache
1616
* We assume that the file has this structure:
1717
* <p/>
1818
* IP address HW type Flags HW address Mask Device

library/src/main/java/com/stealthcopter/networktools/IPTools.java

Lines changed: 53 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
11
package com.stealthcopter.networktools;
22

3+
import android.support.annotation.Nullable;
4+
5+
import java.net.Inet4Address;
6+
import java.net.InetAddress;
7+
import java.net.NetworkInterface;
8+
import java.net.SocketException;
9+
import java.util.ArrayList;
10+
import java.util.Enumeration;
311
import java.util.regex.Pattern;
412

513
/**
@@ -30,20 +38,57 @@ private IPTools() {
3038
Pattern.compile(
3139
"^((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)::((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)$");
3240

33-
public static boolean isIPv4Address(final String input) {
34-
return IPV4_PATTERN.matcher(input).matches();
41+
public static boolean isIPv4Address(final String address) {
42+
return IPV4_PATTERN.matcher(address).matches();
43+
}
44+
45+
public static boolean isIPv6StdAddress(final String address) {
46+
return IPV6_STD_PATTERN.matcher(address).matches();
3547
}
3648

37-
public static boolean isIPv6StdAddress(final String input) {
38-
return IPV6_STD_PATTERN.matcher(input).matches();
49+
public static boolean isIPv6HexCompressedAddress(final String address) {
50+
return IPV6_HEX_COMPRESSED_PATTERN.matcher(address).matches();
3951
}
4052

41-
public static boolean isIPv6HexCompressedAddress(final String input) {
42-
return IPV6_HEX_COMPRESSED_PATTERN.matcher(input).matches();
53+
public static boolean isIPv6Address(final String address) {
54+
return isIPv6StdAddress(address) || isIPv6HexCompressedAddress(address);
55+
}
56+
57+
/**
58+
* @return The first local IPv4 address, or null
59+
*/
60+
@Nullable
61+
public static InetAddress getLocalIPv4Address() {
62+
ArrayList<InetAddress> localAddresses = getLocalIPv4Addresses();
63+
return localAddresses.size() > 0 ? localAddresses.get(0) : null;
4364
}
4465

45-
public static boolean isIPv6Address(final String input) {
46-
return isIPv6StdAddress(input) || isIPv6HexCompressedAddress(input);
66+
/**
67+
* @return The list of all IPv4 addresses found
68+
*/
69+
public static ArrayList<InetAddress> getLocalIPv4Addresses() {
70+
71+
ArrayList<InetAddress> foundAddresses = new ArrayList<>();
72+
73+
Enumeration<NetworkInterface> ifaces;
74+
try {
75+
ifaces = NetworkInterface.getNetworkInterfaces();
76+
77+
while (ifaces.hasMoreElements()) {
78+
NetworkInterface iface = ifaces.nextElement();
79+
Enumeration<InetAddress> addresses = iface.getInetAddresses();
80+
81+
while (addresses.hasMoreElements()) {
82+
InetAddress addr = addresses.nextElement();
83+
if (addr instanceof Inet4Address && !addr.isLoopbackAddress()) {
84+
foundAddresses.add(addr);
85+
}
86+
}
87+
}
88+
} catch (SocketException e) {
89+
e.printStackTrace();
90+
}
91+
return foundAddresses;
4792
}
4893

4994

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
package com.stealthcopter.networktools;
2+
3+
import android.support.annotation.NonNull;
4+
5+
import com.stealthcopter.networktools.ping.PingResult;
6+
import com.stealthcopter.networktools.subnet.SubnetInfo;
7+
8+
import java.net.InetAddress;
9+
import java.net.UnknownHostException;
10+
import java.util.ArrayList;
11+
import java.util.concurrent.ExecutorService;
12+
import java.util.concurrent.Executors;
13+
import java.util.concurrent.TimeUnit;
14+
15+
/**
16+
* Created by mat on 03/11/17.
17+
*/
18+
public class SubnetDevices {
19+
private int noThreads = 255;
20+
21+
private ArrayList<String> addresses;
22+
private ArrayList<SubnetInfo> devicesFound;
23+
private OnSubnetDeviceFound listener;
24+
25+
// This class is not to be instantiated
26+
private SubnetDevices() {
27+
}
28+
29+
public interface OnSubnetDeviceFound {
30+
void onDeviceFound(SubnetInfo subnetInfo);
31+
void onFinished(ArrayList<SubnetInfo> devicesFound);
32+
}
33+
34+
/**
35+
* Find devices on the subnet working from the local device ip address
36+
*/
37+
public static SubnetDevices fromLocalAddress() {
38+
InetAddress ipv4 = IPTools.getLocalIPv4Address();
39+
40+
if (ipv4 == null){
41+
throw new IllegalAccessError("Could not access local ip address");
42+
}
43+
44+
return fromIPAddress(ipv4.getHostAddress());
45+
}
46+
47+
/**
48+
* @param inetAddress - an ip address in the subnet
49+
*/
50+
public static SubnetDevices fromIPAddress(@NonNull InetAddress inetAddress) {
51+
return fromIPAddress(inetAddress.getHostAddress());
52+
}
53+
54+
/**
55+
* @param ipAddress - the ipAddress string of any device in the subnet i.e. "192.168.0.1"
56+
* the final part will be ignored
57+
*/
58+
public static SubnetDevices fromIPAddress(@NonNull final String ipAddress) {
59+
60+
if (!IPTools.isIPv4Address(ipAddress)){
61+
throw new IllegalArgumentException("Invalid IP Address");
62+
}
63+
64+
SubnetDevices subnetDevice = new SubnetDevices();
65+
66+
subnetDevice.addresses = new ArrayList<>();
67+
68+
// Get addresses from ARP Info first as they are likely to be pingable
69+
subnetDevice.addresses.addAll(ARPInfo.getAllIPAddressesInARPCache());
70+
71+
// Add all missing addresses in subnet
72+
for (int j = 0; j < 255; j++) {
73+
if (!subnetDevice.addresses.contains(ipAddress + j)) {
74+
subnetDevice.addresses.add(ipAddress + j);
75+
}
76+
}
77+
78+
return subnetDevice;
79+
80+
}
81+
82+
/**
83+
*
84+
* @param noThreads set the number of threads to work with, note we default to a large number
85+
* as these requests are network heavy not cpu heavy.
86+
* @return self
87+
* @throws IllegalAccessException
88+
*/
89+
public SubnetDevices setNoThreads(int noThreads) throws IllegalAccessException {
90+
if (noThreads < 1) throw new IllegalArgumentException("Cannot have less than 1 thread");
91+
this.noThreads = noThreads;
92+
return this;
93+
}
94+
95+
public void findDevices(final OnSubnetDeviceFound listener) {
96+
97+
this.listener = listener;
98+
99+
devicesFound = new ArrayList<>();
100+
101+
ExecutorService executor = Executors.newFixedThreadPool(this.noThreads);
102+
103+
for (final String add : addresses) {
104+
Runnable worker = new SubnetDeviceFinderRunnable(add);
105+
executor.execute(worker);
106+
}
107+
108+
// This will make the executor accept no new threads
109+
// and finish all existing threads in the queue
110+
executor.shutdown();
111+
// Wait until all threads are finish
112+
try {
113+
executor.awaitTermination(1, TimeUnit.HOURS);
114+
} catch (InterruptedException e) {
115+
e.printStackTrace();
116+
}
117+
118+
this.listener.onFinished(devicesFound);
119+
}
120+
121+
private synchronized void subnetDeviceFound(SubnetInfo subnetInfo){
122+
devicesFound.add(subnetInfo);
123+
listener.onDeviceFound(subnetInfo);
124+
}
125+
126+
public class SubnetDeviceFinderRunnable implements Runnable {
127+
private final String address;
128+
129+
SubnetDeviceFinderRunnable(String address) {
130+
this.address = address;
131+
}
132+
133+
@Override
134+
public void run() {
135+
try {
136+
InetAddress ia = InetAddress.getByName(address);
137+
PingResult pingResult = Ping.onAddress(ia).doPing();
138+
if (pingResult.isReachable) {
139+
SubnetInfo subnetInfo = new SubnetInfo(ia);
140+
subnetInfo.mac = ARPInfo.getMACFromIPAddress(ia.getHostAddress());
141+
subnetInfo.time = pingResult.timeTaken;
142+
subnetDeviceFound(subnetInfo);
143+
}
144+
} catch (UnknownHostException e) {
145+
e.printStackTrace();
146+
}
147+
}
148+
}
149+
150+
}

0 commit comments

Comments
 (0)