Skip to content

Commit 56b1a16

Browse files
committed
Add checks for getting network interfaces that supports multicast, should network code needs to send Datagram packets
Fixes #3778
1 parent cc29992 commit 56b1a16

File tree

5 files changed

+259
-7
lines changed

5 files changed

+259
-7
lines changed

app/src/main/java/com/amaze/filemanager/ui/notifications/FtpNotification.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ public static void updateNotification(Context context, boolean noStopButton) {
103103
boolean secureConnection =
104104
sharedPreferences.getBoolean(FtpService.KEY_PREFERENCE_SECURE, FtpService.DEFAULT_SECURE);
105105

106-
InetAddress address = NetworkUtil.getLocalInetAddress(context);
106+
InetAddress address = NetworkUtil.getLocalInetAddress(context, false);
107107

108108
String address_text = "Address not found";
109109

app/src/main/java/com/amaze/filemanager/utils/NetworkUtil.kt

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,10 @@ object NetworkUtil {
100100
* Caveat: doesn't handle IPv6 addresses well. Forcing return IPv4 if possible.
101101
*/
102102
@JvmStatic
103-
fun getLocalInetAddress(context: Context): InetAddress? {
103+
fun getLocalInetAddress(
104+
context: Context,
105+
requestMulticast: Boolean = false,
106+
): InetAddress? {
104107
if (!isConnectedToLocalNetwork(context)) {
105108
return null
106109
}
@@ -112,14 +115,25 @@ object NetworkUtil {
112115
return if (ipAddress == 0) null else intToInet(ipAddress)
113116
}
114117
runCatching {
115-
NetworkInterface.getNetworkInterfaces().iterator().forEach { netinterface ->
116-
netinterface.inetAddresses.iterator().forEach { address ->
118+
NetworkInterface.getNetworkInterfaces().iterator().forEach { networkInterface ->
119+
networkInterface.inetAddresses.iterator().forEach { address ->
117120
// this is the condition that sometimes gives problems
118121
if (!address.isLoopbackAddress &&
119122
!address.isLinkLocalAddress &&
120123
address is Inet4Address
121124
) {
122-
return address
125+
if (requestMulticast) {
126+
if (networkInterface.supportsMulticast()) {
127+
return address
128+
} else {
129+
log.warn(
130+
"network interface {} does not support multicast",
131+
networkInterface.displayName,
132+
)
133+
}
134+
} else {
135+
return address
136+
}
123137
}
124138
}
125139
}

app/src/main/java/com/amaze/filemanager/utils/smb/SameSubnetDiscoverDeviceStrategy.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ class SameSubnetDiscoverDeviceStrategy : SmbDeviceScannerObservable.DiscoverDevi
9595
}
9696

9797
private fun getNeighbourhoodHosts(): List<InetAddress> {
98-
val deviceAddress = NetworkUtil.getLocalInetAddress(AppConfig.getInstance())
98+
val deviceAddress = NetworkUtil.getLocalInetAddress(AppConfig.getInstance(), requestMulticast = true)
9999
return deviceAddress?.let { addr ->
100100
if (addr is Inet6Address) {
101101
// IPv6 neigbourhood hosts can be very big - that should use wsdd instead; hence

app/src/main/java/com/amaze/filemanager/utils/smb/WsddDiscoverDeviceStrategy.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,7 @@ class WsddDiscoverDeviceStrategy : SmbDeviceScannerObservable.DiscoverDeviceStra
103103

104104
@Suppress("LabeledExpression")
105105
private fun multicastForDevice(callback: (ComputerParcelable) -> Unit) {
106-
NetworkUtil.getLocalInetAddress(AppConfig.getInstance())?.let { addr ->
106+
NetworkUtil.getLocalInetAddress(AppConfig.getInstance(), requestMulticast = true)?.let { addr ->
107107
val multicastAddressV4 = InetAddress.getByName(BROADCAST_IPV4)
108108
val multicastAddressV6 = InetAddress.getByName(BROADCAST_IPV6_LINK_LOCAL)
109109

Lines changed: 238 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
1+
package com.amaze.filemanager.utils
2+
3+
import android.app.Service
4+
import android.content.Context
5+
import android.net.ConnectivityManager
6+
import android.net.Network
7+
import android.net.NetworkCapabilities
8+
import android.net.NetworkInfo
9+
import android.net.wifi.WifiInfo
10+
import android.net.wifi.WifiManager
11+
import android.os.Build
12+
import android.os.Build.VERSION_CODES
13+
import android.os.Build.VERSION_CODES.LOLLIPOP
14+
import android.os.Build.VERSION_CODES.P
15+
import androidx.test.ext.junit.runners.AndroidJUnit4
16+
import io.mockk.every
17+
import io.mockk.mockk
18+
import io.mockk.mockkStatic
19+
import io.mockk.unmockkAll
20+
import org.junit.After
21+
import org.junit.Assert.assertEquals
22+
import org.junit.Assert.assertNotNull
23+
import org.junit.Assert.assertNull
24+
import org.junit.Before
25+
import org.junit.Test
26+
import org.junit.runner.RunWith
27+
import org.robolectric.annotation.Config
28+
import java.net.Inet4Address
29+
import java.net.NetworkInterface
30+
import java.util.Collections
31+
32+
@RunWith(AndroidJUnit4::class)
33+
@Config(sdk = [LOLLIPOP, P, VERSION_CODES.R])
34+
class NetworkUtilTest {
35+
private lateinit var context: Context
36+
private lateinit var connectivityManager: ConnectivityManager
37+
private lateinit var wifiManager: WifiManager
38+
private lateinit var wifiInfo: WifiInfo
39+
40+
@Before
41+
fun setUp() {
42+
context = mockk(relaxed = true)
43+
connectivityManager = mockk(relaxed = true)
44+
wifiManager = mockk(relaxed = true)
45+
wifiInfo = mockk(relaxed = true)
46+
47+
every { context.applicationContext } returns context
48+
every {
49+
context.getSystemService(Service.CONNECTIVITY_SERVICE)
50+
} returns connectivityManager
51+
every {
52+
context.getSystemService(Service.WIFI_SERVICE)
53+
} returns wifiManager
54+
every { wifiManager.connectionInfo } returns wifiInfo
55+
}
56+
57+
@Test
58+
fun testGetLocalInetAddressWhenNotConnected() {
59+
mockNetworkNotConnected()
60+
assertNull(NetworkUtil.getLocalInetAddress(context))
61+
}
62+
63+
@Test
64+
fun testGetLocalInetAddressOnWifi() {
65+
mockWifiConnected()
66+
every { wifiInfo.ipAddress } returns 0x0F02000A // 10.0.2.15
67+
68+
val result = NetworkUtil.getLocalInetAddress(context)
69+
assertNotNull(result)
70+
assertEquals("10.0.2.15", result?.hostAddress)
71+
}
72+
73+
@Test
74+
fun testGetLocalInetAddressOnEthernet() {
75+
mockEthernetConnected()
76+
mockkStatic(NetworkInterface::class)
77+
78+
val inetAddress = mockk<Inet4Address>()
79+
every { inetAddress.isLoopbackAddress } returns false
80+
every { inetAddress.isLinkLocalAddress } returns false
81+
every { inetAddress.hostAddress } returns "192.168.1.100"
82+
83+
val networkInterface = mockk<NetworkInterface>()
84+
every { networkInterface.inetAddresses } returns
85+
Collections.enumeration(listOf(inetAddress))
86+
every { NetworkInterface.getNetworkInterfaces() } returns
87+
Collections.enumeration(listOf(networkInterface))
88+
89+
val result = NetworkUtil.getLocalInetAddress(context)
90+
assertNotNull(result)
91+
assertEquals("192.168.1.100", result?.hostAddress)
92+
}
93+
94+
@Test
95+
fun testGetLocalInetAddressWithMulticastSupport() {
96+
mockEthernetConnected()
97+
mockkStatic(NetworkInterface::class)
98+
99+
val inetAddress = mockk<Inet4Address>()
100+
every { inetAddress.isLoopbackAddress } returns false
101+
every { inetAddress.isLinkLocalAddress } returns false
102+
every { inetAddress.hostAddress } returns "192.168.1.1"
103+
104+
val networkInterface = mockk<NetworkInterface>()
105+
every { networkInterface.supportsMulticast() } returns true
106+
every { networkInterface.inetAddresses } returns
107+
Collections.enumeration(listOf(inetAddress))
108+
every { NetworkInterface.getNetworkInterfaces() } returns
109+
Collections.enumeration(listOf(networkInterface))
110+
111+
val result = NetworkUtil.getLocalInetAddress(context, requestMulticast = true)
112+
assertNotNull(result)
113+
assertEquals("192.168.1.1", result?.hostAddress)
114+
}
115+
116+
@Test
117+
fun testGetLocalInetAddressWithoutMulticastSupport() {
118+
mockEthernetConnected()
119+
120+
val inetAddress = mockk<Inet4Address>()
121+
every { inetAddress.isLoopbackAddress } returns false
122+
every { inetAddress.isLinkLocalAddress } returns false
123+
every { inetAddress.hostAddress } returns "192.168.1.100"
124+
125+
val networkInterface = mockk<NetworkInterface>()
126+
every { networkInterface.supportsMulticast() } returns false
127+
every { networkInterface.displayName } returns "eth0"
128+
every { networkInterface.inetAddresses } returns
129+
Collections.enumeration(listOf(inetAddress))
130+
131+
mockkStatic(NetworkInterface::class)
132+
every { NetworkInterface.getNetworkInterfaces() } returns
133+
Collections.enumeration(listOf(networkInterface))
134+
135+
val result = NetworkUtil.getLocalInetAddress(context, requestMulticast = true)
136+
assertNull(result)
137+
}
138+
139+
@Test
140+
fun testGetLocalInetAddressWithMultipleInterfacesOneSupportsMulticast() {
141+
mockEthernetConnected()
142+
mockkStatic(NetworkInterface::class)
143+
144+
// Create a non-multicast interface
145+
val nonMulticastAddress = mockk<Inet4Address>()
146+
every { nonMulticastAddress.isLoopbackAddress } returns false
147+
every { nonMulticastAddress.isLinkLocalAddress } returns false
148+
every { nonMulticastAddress.hostAddress } returns "192.168.1.100"
149+
150+
val nonMulticastInterface = mockk<NetworkInterface>()
151+
every { nonMulticastInterface.supportsMulticast() } returns false
152+
every { nonMulticastInterface.displayName } returns "eth0"
153+
every { nonMulticastInterface.inetAddresses } returns
154+
Collections.enumeration(listOf(nonMulticastAddress))
155+
156+
// Create a multicast-capable interface
157+
val multicastAddress = mockk<Inet4Address>()
158+
every { multicastAddress.isLoopbackAddress } returns false
159+
every { multicastAddress.isLinkLocalAddress } returns false
160+
every { multicastAddress.hostAddress } returns "192.168.2.100"
161+
162+
val multicastInterface = mockk<NetworkInterface>()
163+
every { multicastInterface.supportsMulticast() } returns true
164+
every { multicastInterface.displayName } returns "wlan0"
165+
every { multicastInterface.inetAddresses } returns
166+
Collections.enumeration(listOf(multicastAddress))
167+
168+
// Mock NetworkInterface.getNetworkInterfaces() to return both interfaces
169+
every { NetworkInterface.getNetworkInterfaces() } returns
170+
Collections.enumeration(listOf(nonMulticastInterface, multicastInterface))
171+
172+
val result = NetworkUtil.getLocalInetAddress(context, requestMulticast = true)
173+
assertNotNull(result)
174+
assertEquals("192.168.2.100", result?.hostAddress)
175+
}
176+
177+
private fun mockNetworkNotConnected() {
178+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
179+
val networkCapabilities = mockk<NetworkCapabilities>()
180+
every { networkCapabilities.hasTransport(any()) } returns false
181+
every { connectivityManager.activeNetwork } returns null
182+
every {
183+
connectivityManager.getNetworkCapabilities(any())
184+
} returns networkCapabilities
185+
} else {
186+
every { connectivityManager.activeNetworkInfo } returns null
187+
}
188+
}
189+
190+
private fun mockWifiConnected() {
191+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
192+
val network = mockk<Network>()
193+
val networkCapabilities = mockk<NetworkCapabilities>()
194+
every {
195+
networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)
196+
} returns true
197+
every {
198+
networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET)
199+
} returns false
200+
every { connectivityManager.activeNetwork } returns network
201+
every {
202+
connectivityManager.getNetworkCapabilities(network)
203+
} returns networkCapabilities
204+
} else {
205+
val networkInfo = mockk<NetworkInfo>()
206+
every { networkInfo.isConnected } returns true
207+
every { networkInfo.type } returns ConnectivityManager.TYPE_WIFI
208+
every { connectivityManager.activeNetworkInfo } returns networkInfo
209+
}
210+
}
211+
212+
private fun mockEthernetConnected() {
213+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
214+
val network = mockk<Network>()
215+
val networkCapabilities = mockk<NetworkCapabilities>()
216+
every {
217+
networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI)
218+
} returns false
219+
every {
220+
networkCapabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET)
221+
} returns true
222+
every { connectivityManager.activeNetwork } returns network
223+
every {
224+
connectivityManager.getNetworkCapabilities(network)
225+
} returns networkCapabilities
226+
} else {
227+
val networkInfo = mockk<NetworkInfo>()
228+
every { networkInfo.isConnected } returns true
229+
every { networkInfo.type } returns ConnectivityManager.TYPE_ETHERNET
230+
every { connectivityManager.activeNetworkInfo } returns networkInfo
231+
}
232+
}
233+
234+
@After
235+
fun tearDown() {
236+
unmockkAll()
237+
}
238+
}

0 commit comments

Comments
 (0)