Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement the option to scan all Port #104

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .idea/compiler.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions .idea/deploymentTargetDropDown.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions .idea/deploymentTargetSelector.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 2 additions & 4 deletions .idea/gradle.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions .idea/kotlinc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 10 additions & 0 deletions .idea/migrations.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions .idea/misc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ apply plugin: 'kotlin-kapt'

android {
compileSdkVersion 31
buildToolsVersion "30.0.2"
defaultConfig {
applicationId "de.csicar.ning"
minSdkVersion 23
Expand All @@ -24,6 +23,7 @@ android {
buildFeatures {
viewBinding true
}
namespace 'de.csicar.ning'

}

Expand Down
3 changes: 1 addition & 2 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="de.csicar.ning">
xmlns:tools="http://schemas.android.com/tools">

<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
Expand Down
4 changes: 2 additions & 2 deletions app/src/main/java/de/csicar/ning/Dao.kt
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ interface PortDao {
fun insert(port: Port): Long

@Transaction
suspend fun upsert(port: Port): Long {
fun upsert(port: Port): Long {
val portFromDB = getPortFromNumber(port.deviceId, port.port) ?: return insert(port)

update(Port(portFromDB.portId, port.port, port.protocol, port.deviceId))
Expand All @@ -135,7 +135,7 @@ interface PortDao {
@Dao
interface ScanDao {
@Insert
suspend fun insert(scan: Scan): Long
fun insert(scan: Scan): Long

@Query("Select * FROM SCAN")
fun getAll(): LiveData<List<Scan>>
Expand Down
63 changes: 56 additions & 7 deletions app/src/main/java/de/csicar/ning/DeviceInfoFragment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,26 @@ package de.csicar.ning
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.ProgressBar
import android.widget.TextView
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.fragment.app.viewModels
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import de.csicar.ning.scanner.PortScanner
import de.csicar.ning.ui.RecyclerViewCommon
import de.csicar.ning.util.AppPreferences
import de.csicar.ning.util.CopyUtil
//import kotlinx.android.synthetic.main.fragment_port_item.view.*
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.InternalCoroutinesApi
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext

Expand All @@ -30,6 +33,10 @@ import kotlinx.coroutines.withContext
* [DeviceInfoFragment.OnListFragmentInteractionListener] interface.
*/
class DeviceInfoFragment : Fragment() {
companion object {
val TAG: String = DeviceInfoFragment::class.java.name
}

val viewModel: ScanViewModel by activityViewModels()
lateinit var scanAllPortsButton: Button

Expand All @@ -47,15 +54,18 @@ class DeviceInfoFragment : Fragment() {
val deviceNameTextView = view.findViewById<TextView>(R.id.deviceNameTextView)
val deviceHwAddressTextView = view.findViewById<TextView>(R.id.deviceHwAddressTextView)
val deviceVendorTextView = view.findViewById<TextView>(R.id.deviceVendorTextView)
val portScanProgressBar = view.findViewById<ProgressBar>(R.id.portScanProgressBar)

copyUtil.makeTextViewCopyable((deviceTypeTextView))
copyUtil.makeTextViewCopyable((deviceIpTextView))
copyUtil.makeTextViewCopyable(deviceNameTextView)
copyUtil.makeTextViewCopyable(deviceHwAddressTextView)
copyUtil.makeTextViewCopyable(deviceVendorTextView)

viewModel.deviceDao.getById(argumentDeviceId).observe(this, Observer {
fetchInfo(it.asDevice)
viewModel.deviceDao.getById(argumentDeviceId).observe(viewLifecycleOwner, Observer {
fetchCommonPorts(it.asDevice) { progress ->
portScanProgressBar.progress = (progress * 1000).toInt()
}
deviceTypeTextView.text = getString(it.deviceType.label)
deviceIpTextView.text = it.ip.hostAddress
deviceNameTextView.text = if (it.isScanningDevice) {
Expand All @@ -70,7 +80,18 @@ class DeviceInfoFragment : Fragment() {

val ports = viewModel.portDao.getAllForDevice(argumentDeviceId)

recyclerView.setHandler(context!!, this, object :
this.scanAllPortsButton = view.findViewById(R.id.scanAllPorts)

this.scanAllPortsButton.setOnClickListener {
viewModel.viewModelScope.launch(context = Dispatchers.IO) {
Log.d(TAG, "scan all ports")
fetchAllPorts(viewModel.deviceDao.getByIdNow(argumentDeviceId)) {
portScanProgressBar.progress = (it * 1000).toInt()
}
}
}

recyclerView.setHandler(requireContext(), this, object :
RecyclerViewCommon.Handler<Port>(R.layout.fragment_port_item, ports) {
override fun shareIdentity(a: Port, b: Port) = a.port == b.port
override fun areContentsTheSame(a: Port, b: Port) = a == b
Expand Down Expand Up @@ -118,12 +139,40 @@ class DeviceInfoFragment : Fragment() {
return view
}

fun fetchInfo(device: Device) {
private fun fetchAllPorts(device: Device, onProgress: (Double) -> Unit) {
viewModel.viewModelScope.launch {
withContext(Dispatchers.IO) {
Log.d(TAG, "fetchAllPort")
val portScans = PortScanner(device.ip).scanAllPorts()
var currentProgress = 0.0

portScans.onEach {
currentProgress += it.progressPortion
Log.d(TAG, "from progress scan $it ($currentProgress)")
onProgress(currentProgress)
}.collect { result: PortScanner.PortResult ->
if (result.isOpen) {
viewModel.portDao.upsert(
Port(0, result.port, result.protocol, device.deviceId)
)
}
}
}
}
}

private fun fetchCommonPorts(device: Device, onProgress: (Double) -> Unit) {
viewModel.viewModelScope.launch {
withContext(Dispatchers.IO) {
PortScanner(device.ip).scanPorts().forEach {
val portScans = PortScanner(device.ip).scanCommonPorts()
var currentProgress = 0.0
portScans.forEach {
launch {
val result = it.await()

currentProgress += 1.0 / portScans.size
onProgress(currentProgress)

if (result.isOpen) {
viewModel.portDao.upsert(
Port(
Expand Down
45 changes: 33 additions & 12 deletions app/src/main/java/de/csicar/ning/scanner/PortScanner.kt
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
package de.csicar.ning.scanner

import android.util.Log
import de.csicar.ning.DeviceInfoFragment
import de.csicar.ning.Port
import de.csicar.ning.PortDescription
import de.csicar.ning.Protocol
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.asFlow
import kotlinx.coroutines.flow.flatMapConcat
import kotlinx.coroutines.flow.flatMapMerge
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.withContext
import java.io.IOException
import java.io.InterruptedIOException
Expand Down Expand Up @@ -48,24 +54,39 @@ class PortScanner(val ip: InetAddress) {
return@withContext false
} catch (ex: NoRouteToHostException) {
Log.d(TAG, "No Route to Host: $ex")
return@withContext false
return@withContext false
} finally {
socket?.close()
}
}

suspend fun scanPorts() = withContext(Dispatchers.Main) {
PortDescription.commonPorts.flatMap {
listOf(
async {
PortResult(it.port, Protocol.TCP, isTcpPortOpen(it.port))
},
async {
PortResult(it.port, Protocol.UDP, isUdpPortOpen(it.port))
}
)
suspend fun scanAllPorts(): Flow<PortResult> {
val range = (1..65535)
return range.asFlow().flatMapMerge(concurrency = 128) {
Log.d(DeviceInfoFragment.TAG, "scan port $it")
flow {
emit(PortResult(it, Protocol.TCP, isTcpPortOpen(it), 1.0 / range.count()))
emit(PortResult(it, Protocol.UDP, isUdpPortOpen(it), 1.0 / range.count()))
}
}
}


suspend fun scanCommonPorts() = withContext(Dispatchers.Main) {
val ports = PortDescription.commonPorts
ports.flatMap {
listOf(async {
PortResult(it.port, Protocol.TCP, isTcpPortOpen(it.port), 1.0 / ports.count())
}, async {
PortResult(it.port, Protocol.UDP, isUdpPortOpen(it.port), 1.0 / ports.count())
})
}
}

data class PortResult(val port: Int, val protocol: Protocol, val isOpen: Boolean)
data class PortResult(
val port: Int,
val protocol: Protocol,
val isOpen: Boolean,
val progressPortion: Double
)
}
19 changes: 18 additions & 1 deletion app/src/main/res/layout/fragment_deviceinfo_list.xml
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,13 @@
android:text="@string/title_open_ports"
android:textAppearance="@style/TextAppearance.AppCompat.Body2" />

<ProgressBar
android:id="@+id/portScanProgressBar"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="match_parent"
android:max="1000"
tools:progress="300"
android:layout_height="wrap_content"/>
<de.csicar.ning.ui.RecyclerViewCommon
android:id="@+id/list"
android:name="de.csicar.ning.DeviceInfoFragment"
Expand All @@ -154,6 +161,16 @@
android:nestedScrollingEnabled="false"
app:layoutManager="LinearLayoutManager"
tools:context=".DeviceInfoFragment"
tools:listitem="@layout/fragment_port_item"/>
tools:listitem="@layout/fragment_port_item">

</de.csicar.ning.ui.RecyclerViewCommon>

<Button
android:id="@+id/scanAllPorts"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:gravity="center_horizontal"
android:text="@string/scanAllPorts" />
</LinearLayout>
</androidx.core.widget.NestedScrollView>
1 change: 1 addition & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,5 @@
<string name="device_type_cast">Cast</string>
<string name="device_type_unknown">Unknown</string>
<string name="device_type_home_appliance">Home Appliance</string>
<string name="scanAllPorts">more Ports...</string>
</resources>
5 changes: 5 additions & 0 deletions app/src/test/java/de/csicar/ning/scanner/PortScannerTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package de.csicar.ning.scanner

import org.junit.jupiter.api.Assertions.*

class PortScannerTest
4 changes: 2 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules.

buildscript {
ext.kotlin_version = '1.5.31'
ext.kotlin_version = '1.6.21'
repositories {
google()
mavenCentral()

}
dependencies {
classpath 'com.android.tools.build:gradle:7.0.3'
classpath 'com.android.tools.build:gradle:8.4.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
Expand Down
Loading